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