2019.10.21 csp-s模拟测试81 反思总结

T1:

把每一行状压,按行DP。设fi,j,k,i表示第几行,j是当前行的1覆盖状态,k是当前行选择按钮的状态。转移的时候枚举j和k,再枚举下一层的按钮选择情况l。如果l和j可以全覆盖当前层则转移合法,根据下一层选择l状态的代价进行转移。预处理一行每一种选法i可以覆盖到的状态di,各行选择按钮状态i对应的代价dpi,以及每一行的初始状态bi。转移时下一层的覆盖情况就是k|dl|bi+1。初始化第一层是所有选法i对应的代价,即f1,d[i]|b[1],i=dp1,i

整个DP过程的复杂度是O(3m*2m*n)。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=11,maxn=1<<N;
int n,maxx,ans,m,flag;
int s[N][N],a[N][N],dp[N][maxn],f[N][maxn][maxn],b[N],d[maxn];
int main()
{
    scanf("%d%d",&n,&m);
    maxx=1<<m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%1d",&s[i][j]);
            b[i]|=(s[i][j]<<(j-1));
        }
    }
    for(int i=0;i<maxx;i++){
        d[i]=(i|(i<<1)|(i>>1))&(maxx-1);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
        }
        for(int j=0;j<maxx;j++){
            for(int k=1;k<=m;k++){
                if(j&(1<<(k-1))){
                    dp[i][j]+=a[i][k];
                }
            }
        }
    }
    memset(f,0x3f3f3f3f,sizeof(f));
    for(int j=0;j<maxx;j++){
        f[1][d[j]|b[1]][j]=min(f[1][d[j]|b[1]][j],dp[1][j]);
    }
    for(int i=1;i<n;i++){
        for(int j=0;j<maxx;j++){
            if((j&b[i])!=b[i])continue;
            for(int k=j;k>=0;k=(k-1)&j){
                if((d[k]&j)!=d[k]&&k)continue;
                for(int l=0;l<maxx;l++){
                    if((j|l)==(maxx-1)){
                        f[i+1][d[l]|k|b[i+1]][l]=min(f[i+1][d[l]|k|b[i+1]][l],f[i][j][k]+dp[i+1][l]);
                    }
                }
                if(!k)break;
            }
        }
    }
    ans=0x3f3f3f3f;
    for(int i=0;i<maxx;i++)ans=min(ans,f[n][maxx-1][i]);
    printf("%d\n",ans);
    return 0;
}

处理b的时候反着处理了整张图,并且初始化第一行的时候出了锅没考虑b数组,于是写了一下午。

T2:

第一问比较容易。把求方案数的过程看成一个往序列里添加元素的过程。将原序列按第一关键字val从大到小,第二关键字key从小到大排序。val从大到小保证了后添加的元素无论放在哪都不会影响前面放进来的元素,并且已经存在的每一个元素都会对当前元素的key值产生贡献。所以只用考虑当前要放的元素本身的限制,考虑怎么求这一次操作的方案数,即新加入的元素能放在哪些地方。如果序列里的元素个数小于keyi,那么显然最多只有i个位置可以放(1->i)。如果序列里的元素多于等于keyi个,那么也只有前keyi个位置可以放,这样才不会超出key的限制。特别的状况是存在相同的val,这就是前面key作为第二关键字从小到大排序的原因。相同val的key从小到大,保证对于后加入的元素,先加入的val相同的元素所在的位置也可以放当前的元素。先加入的元素为后加入的元素创造了新的位置,它所在的地方后加入的元素一定可以放到。于是对于相同的val累计一个num,num从已有的第二个相同val开始累计,ans=(ans*min(i,keyi+num))%mod。

第二问愣是没想出来,也没看明白题解里平衡树的做法。好像是根据第一问的过程,每次插入一个元素的时候,贪心地放在能放的最大位置之前的第一个字典序比它大的元素前面。然后就不会写了。

题解的第二种做法是利用线段树,也是贪心地构造一个序列。一开始所有元素都是未加入元素。如果已经加入的元素的个数小于手中所有元素的key值-1,那么随便把哪个元素扔进序列都合法。根据题目要求,显然选择未加入的元素中字典序最小的。如果未加入的元素中存在key被卡在界限上,那么要么把它丢进序列,要么在val小于等于它的元素中选择更优的丢进序列。于是可以想到把原序列按val第一关键字从小到大key第二关键字从小到大排序,用线段树维护序列。选择要加入序列的元素就是在线段树的一段区间上选择key最小的元素。每次将一个元素扔进序列后,把线段树上这个元素的所有信息都变成inf,并且把所有未加入序列且val比它小的元素的key值-1,即线段树上的区间减。这里的key其实要记两次,一个记作用来当作限制,进行区间减操作。一个始终不变,用来在区间查询的时候作为可能答案进行比较。更新区间最小限制值以及key值的时候若出现值相同的元素,更新为在线段树上更靠前的,也就是val更小的。一个是构造的序列要求第二关键字val也是从小到大。另一个是若有多个key值同时等于1,要用val最小的来限制答案。否则根据更大的val选择出来的答案可能不满足val小的key的限制。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,mod=1e9+7,num;
const int N=5*1e5+10,inf=2147483647;
long long ans;
struct node{
    int key,val;
}a[N];
struct tree{
    int l,r,dic,dici,val,vali,tag,top,topi;
}b[N*4];
bool cmp(node a,node b){
    if(a.val>b.val)return true;
    else if(a.val==b.val){
        if(a.key<b.key)return true;
        else return false;
    }
    else return false;
}
bool cmp1(node a,node b){
    if(a.val<b.val)return true;
    else if(a.val==b.val){
        if(a.key<b.key)return true;
        else return false;
    }
    else return false;
}
void pushup(int p){
    if(b[p*2].dic<b[p*2+1].dic){
        b[p].dic=b[p*2].dic;
        b[p].dici=b[p*2].dici;
    }
    else if(b[p*2].dic==b[p*2+1].dic){
        b[p].dic=b[p*2].dic;
        if(b[p*2].dici<b[p*2+1].dici){
            b[p].dici=b[p*2].dici;
        }
        else b[p].dici=b[p*2+1].dici;
    }
    else{
        b[p].dic=b[p*2+1].dic;
        b[p].dici=b[p*2+1].dici;
    }
    if(b[p*2].val<b[p*2+1].val){
        b[p].val=b[p*2].val;
        b[p].vali=b[p*2].vali;
    }
    else if(b[p*2].val==b[p*2+1].val){
        b[p].val=b[p*2].val;
        if(a[b[p*2].vali].key<a[b[p*2+1].vali].key){
            b[p].vali=b[p*2].vali;
        }
        else{
            b[p].vali=b[p*2+1].vali;
        }
    }
    else{
        b[p].val=b[p*2+1].val;
        b[p].vali=b[p*2+1].vali;
    }
    if(b[p*2].top<b[p*2+1].top){
        b[p].top=b[p*2].top;
        b[p].topi=b[p*2].topi;
    }
    else if(b[p*2].top==b[p*2+1].top){
        b[p].top=b[p*2].top;
        if(b[p*2].topi<b[p*2+1].topi){
            b[p].topi=b[p*2].topi;
        }
        else b[p].topi=b[p*2+1].topi;
    }
    else{
        b[p].top=b[p*2+1].top;
        b[p].topi=b[p*2+1].topi;
    }
}
void pushdown(int p){
    if(b[p].tag){
        b[p*2].tag+=b[p].tag;
        b[p*2].dic-=b[p].tag;
        b[p*2+1].tag+=b[p].tag;
        b[p*2+1].dic-=b[p].tag;
        b[p].tag=0;
    }
}
void build(int p,int l,int r){
    b[p].l=l,b[p].r=r;
    if(l==r){
        b[p].dic=a[l].key,b[p].dici=l;
        b[p].val=a[l].val,b[p].vali=l;
        b[p].top=a[l].key,b[p].topi=l;
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    pushup(p);
}
void change(int p,int l,int r,int y){
    if(l<=b[p].l&&b[p].r<=r){
        b[p].dic=b[p].val=b[p].top=y;
        a[b[p].l].key=y;
        return;
    }
    pushdown(p);
    int mid=(b[p].l+b[p].r)/2;
    if(l<=mid)change(p*2,l,r,y);
    if(r>mid)change(p*2+1,l,r,y);
    pushup(p);
}
void change1(int p,int l,int r,int y){
    if(l<=b[p].l&&b[p].r<=r){
        b[p].dic--;
        b[p].tag++;
        return;
    }
    pushdown(p);
    int mid=(b[p].l+b[p].r)/2;
    if(l<=mid)change1(p*2,l,r,y);
    if(r>mid)change1(p*2+1,l,r,y);
    pushup(p);
}
int ask(int p,int l,int r){
    if(l<=b[p].l&&b[p].r<=r){
        return b[p].topi;
    }
    pushdown(p);
    int mid=(b[p].l+b[p].r)/2;
    int x=0;
    a[x].key=a[x].val=inf;
    if(l<=mid){
        int y=ask(p*2,l,r);
        if(a[y].key<a[x].key)x=y;
        else if(a[y].key==a[x].key){
            if(a[y].val<a[x].val)x=y;
        }
    }
    if(r>mid){
        int y=ask(p*2+1,l,r);
        if(a[y].key<a[x].key)x=y;
        else if(a[y].key==a[x].key){
            if(a[y].val<a[x].val)x=y;
        }
    }
    pushup(p);
    return x;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].key,&a[i].val);
    }
    sort(a+1,a+n+1,cmp);
    ans=1;
    for(int i=1;i<=n;i++){
        if(i>1&&a[i].val==a[i-1].val)num++;
        else num=0;
        ans=ans*min(i,a[i].key+num)%mod;
    }
    printf("%lld\n",ans);
    sort(a+1,a+n+1,cmp1);
    build(1,1,n);
    for(int i=1,x;i<=n;i++){
        if(b[1].dic==1)x=ask(1,1,b[1].dici);
        else x=b[1].topi;
        printf("%d %d\n",a[x].key,a[x].val);
        if(x>1)change1(1,1,x-1,-1);
        change(1,x,x,inf);
    }
    return 0;
}

第一问没想多长时间,第二问也没改多长时间(至少比我作为全场倒数改出来的T1要强),但是考场上就是没想出第二问怎么写。按理说贪心的思路也不是很难想…

T3:

不会,没写,咕着。

关于像在长郡考试那次一样把自己的电脑卡死机两次还不得不关机一次这件事情我还能说什么呢我给自己找扇窗户吧

关于T1不好好打暴力搜索非要搞玄学状压搜索这件事情我都不用给自己找扇窗户了就地蒸发吧

菜 墨雨笙 菜

原文地址:https://www.cnblogs.com/chloris/p/11715723.html

时间: 2024-08-30 14:21:38

2019.10.21 csp-s模拟测试81 反思总结的相关文章

2019.10.26 csp-s模拟测试88 反思总结

今天的主人公是什么? 60.1K!!!! 先扔代码再更新防止我等会儿一上头不打算写完题解 T1: #include<iostream> #include<cstdio> #include<algorithm> using namespace std; const double inf=214748364; int n,k; double a[100010],f[21][100010]; int main() { scanf("%d%d",&n

2019.10.30 csp-s模拟测试94 反思总结

头一次做图巨的模拟题OWO 自从上一次听图巨讲课然后骗了小礼物以后一直对图巨印象挺好的233 T1: 对于XY取对数=Y*log(x) 对于Y!取对数=log(1*2*3*...*Y)=log1+log2+log3+...+logY 因为数字大小不超过1e5,直接累加最后比较就可以了 #include<iostream> #include<cstdio> #include<cmath> using namespace std; int t,x,y; double a,b

2019/10/17 CSP模拟 总结

T1 补票 Ticket 没什么好说的,不讲了 T2 删数字 Number 很后悔的是其实考场上不仅想出了正解的方程,甚至连优化都想到了,却因为码力不足只打了\(O(n^2)\)暴力,甚至还因为细节挂成了\(40\ pts\) 以后还是应该多写一下码农题 规定一下,下面的\(j\)没有特殊说明,取值范围默认在\(1, i - 1\) 考虑什么情况是合法的,首先最后对答案有贡献的元素组成的序列一定是严格上升的,即\(a_i > a_j\) 然后考虑还有没有别的限制呢 放一个图 看到我们的对答案有贡

2019.10.22 csp-s模拟测试82 反思总结

算了 我在干什么orz T2: #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e5+10,p=1500007,mod=998244353; int n,len,cnt1=1,cnt2=1,lens; char s[2*N],c[2*N]; int tree1[2*N][27],tree2[2*N][27]; unsigned long lon

2019.10.26 CSP%您赛第三场

\(CSP\)凉心模拟^_^ --题源\(lqx.lhc\)等各位蒟蒻 题目名称 比赛 传递消息 开关灯 源文件名 \(competition.cpp\) \(message.cpp\) \(light.cpp\) 输入文件名 \(competition.in\) \(message.in\) \(light.in\) 输出文件名 \(competition.out\) \(message.out\) \(light.out\) 测试点时限 \(1s\) \(1s\) \(2s\) 内存限制 \

2019.9.28 csp-s模拟测试54 反思总结

咕咕咕的冲动如此强烈x T1x: 看完题目想了想,感觉把gcd不为1的强行放在一组,看作一个连通块,最后考虑连通块之间的组合方式就可以了. 然后维护这个连通块可以写并查集可以连边跑dfs怎么着都行… 然而我在处理数字分解质因数这里T掉了,原因是一个很显然的优化写法我基本没怎么写过.线性筛的时候记录每个数是被哪个质数标记过的,分解一个数的时候直接处理记录下来的质数就可以. #include<iostream> #include<cstdio> #include<cmath>

【2019.10.25 OI-Killer的模拟赛】3.鸡数

题目链接 题意: 定义“鸡数”指从高位到低位单调不减的数.求$[a,b]$之间有多少个“鸡数”.$t$组询问. $1\le t\le 10^5,\; 1\le a\le b\le 2^{31}-1$ 分析: 数位DP.设$f[i][j]$表示长度为$i$,最高位是$j$的“鸡数”个数,那么$$f[i][j]=\sum\limits^9_{k=j}f[i-1][k]$$ 且$$f[1][i]=1\;(1\le i\le 9)$$ 那么对于一个长度为$l$的$n$且由低到高位写成$s_{1\dots

2019.9.19 csp-s模拟测试47 反思总结

思路接近正解?都想到了?这都是借口呀. 没有用的,往前走吧. T1:Emotional Flutter 我的做法和题解不太一样,我把s放在最后考虑了. 因为出发以后步幅是一样的,所以每一个黑条可以ban掉一段出发点.把黑条的左右边界%k存成区间,每个黑条可以存一个或者两个区间[跨越k这个边界].然后像以前写区间覆盖的贪心一样按左端点排序,看看有没有长至少为s的空余. 代码: #include<iostream> #include<cstdio> #include<cstrin

2019.9.20 csp-s模拟测试48 反思总结

头疼,不说废话了,祝大家rp++. T1: 暴力枚举,n3. 枚举两个串开始匹配的位置,每一次尽量修改. #include<iostream> #include<cstdio> using namespace std; int n,k,cnt,num,ans; char a[310],b[310]; int main() { scanf("%d%d",&n,&k); scanf("%s",a+1); scanf("%