更新一波题解(最近做的三个dp题)
很久没写题解了,去ec之前来填一填坑,希望能攒攒人品。。。
首先是去年上海F题。。uvalive7143
题意:
给n个人分 m间房子,每个房间的容量是已知的,其中有k对双胞胎,双胞胎可以看作相同的人,问总的方案数
其中n<=10W m=10 2k<=n
思路:
dp状态为, 前i个房子分完还剩多少对双胞胎(被拆散的不算)的方案数,转移结合组合数
代码:
#include <bits/stdc++.h> using namespace std; long long dp[15][110]; int a[15]; int sum[15]; long long f[100010]; long long inv[100010]; const long long mod=1e9+7; long long _pow(long long a,long long b) { long long res=1; while(b) { if(b&1) { res=res*a%mod; } b>>=1; a=a*a%mod; } return res; } long long c(int n,int m) { if(n<m) return 0; if(n<0||m<0) { return 0; } return f[n]*inv[n-m]%mod*inv[m]%mod; } int main() { //freopen("in.txt","r",stdin); int T,cas=1; scanf("%d",&T); f[0]=1; for(int i=1;i<=100000;i++) { f[i]=f[i-1]*i%mod; } for(int i=0;i<=100000;i++) { inv[i]=_pow(f[i],mod-2); } while(T--) { int n,m,k; scanf("%d%d%d",&n,&m,&k); memset(dp,0,sizeof(dp)); sum[0]=0; for(int i=1;i<=m;i++) { scanf("%d",a+i); sum[i]=sum[i-1]+a[i]; } dp[0][k]=1; for(int i=1;i<=m;i++) { for(int j=0;j<=k;j++) { int tot=sum[m]-sum[i-1]; for(int t=0;t<=j;t++) { long long d=dp[i-1][j]*c(j,t)%mod*c(tot-(j-t)*2-t,a[i]-t)%mod; dp[i][j-t]=(dp[i][j-t]+d)%mod; } } } printf("Case #%d: %lld\n",cas++,dp[m][0]); } return 0; }
然后是今年莫斯科赛区 H 。。cf gym 100972
题意:
有10W个数 每个数不超过255,要求一个子序列 a[] 使得子序列中 sum(i xor a[i]) 的值最大 其中 i 是选出的子序列中的下标
思路:
如果范围只有1000那就是傻逼题,现在有10W,需要考虑优化。
先考虑取子序列越长越好,发现有bug。然后想到由于每个值不大于255 显然如果选它,它对答案贡献只跟下标的后8位有关,少取256个和全取下标的后8位就循环了
因此最多少取255个
所以dp状态为 dp[i][j]表示前i个数有j个没取的最大值,转移的时候注意边界
代码:
#include <bits/stdc++.h> using namespace std; long long dp[100010][257]; long long dp1[5010][5010]; int to[256]; int a[100010]; int main() { for(int i=0; i<10; i++) { to['0'+i]=i; } for(int i=0; i<10; i++) { to['A'+i]=10+i; } int n; scanf("%d",&n); for(int i=1; i<=n; i++) { char s[3]; scanf("%s",s); a[i]=0; for(int j=0; j<2; j++) { a[i]=16*a[i]+to[s[j]]; } } long long ans=0; memset(dp,0xcf,sizeof(dp)); dp[0][0]=0; for(int i=1; i<=n; i++) { for(int j=0; j<min(i,257); j++) { dp[i][j]=max(dp[i-1][j]+(a[i]^(i-1-j)),dp[i-1][j-1]); } if(i<257) dp[i][i]=0; } for(int i=0; i<=256; i++) { ans=max(ans,dp[n][i]); } printf("%I64d\n",ans); return 0; }
最后是合肥d题。。hdu5555
题意:
有一个n*n的矩阵,对每个1<=x<=n ( x, 0)处都有一只frog,它们会向上跳,同时每个y ( 1 <= y <= n)有一个跳板,跳板可能是好的(长度为n),也可能是坏的 (长度小于n)。已知frog们最多会跳10个坏的跳板, 现在要在跳板上的某些位置放n个药丸(每个跳板一个),问所有的frog都恰好吃一个药丸 并且能跳出矩阵的方案数
思路:
先考虑坏的跳板,由于每个x坐标最多有十个坏的,可以状压它们,代表这10个有没有被前面的frog用过(即在前面frog的 x 坐标处放过了药丸),我的代码里为1代表没被用
注意在状态转移的过程中,某一个frog可能是在自己的坏跳板上吃了药丸,还可能在 好的跳板上吃了药丸,如果对每个frog 都向好的跳板转移,显然好的跳板是不够用的,那么怎么处理呢?
思考后容易发现其实不需要处理,因为有且只有n个 药丸,如果前面某个跳板 还没有被使用就已经不能用了 显然这是一个非法的状态,那么直接不进行转移就好了
最后的答案就是dp[n][0]* (好的跳板个数)! ,为什么要乘阶乘呢,因为显然好的跳板位置是不同的,如果由不同的frog使用了 那么方案也是不同的
感觉有点说不清楚了,具体见代码
代码:
#include <bits/stdc++.h> using namespace std; const long long mod=105225319; struct node { int l, r; }; vector<node> v; vector<int> g[1010]; long long dp[1010][1<<11]; int l[1010]; int r[1010]; int getnum(int x,int t) { int res=-1; for(int i=0; i<(int)g[t].size(); i++) { if(g[t][i]==x) res=i; } return res; } int bz(int st,int now) { return ((st>>(now+1))<<(now+1)) + (st&((1<<now)-1)); } long long f[1010]; int main() { f[0]=1; for(int i=1; i<=1000; i++) { f[i]=f[i-1]*i%mod; } freopen("in.txt","r",stdin); int T,cas=1; scanf("%d",&T); while(T--) { v.clear(); for(int i=1; i<=1000; i++) { g[i].clear(); } int nn=0; int n,x,y; scanf("%d",&n); for(int i=0; i<n; i++) { scanf("%d",l+i); } for(int i=0; i<n; i++) { scanf("%d",r+i); } for(int i=0; i<n; i++) { x=l[i],y=r[i]; if(y-x+1<n) { v.push_back(node{x,y}); } else { nn++; } } for(int i=0; i<(int)v.size(); i++) { for(int j=v[i].l; j<=v[i].r; j++) { g[j].push_back(i); } } int ok=1; for(int i=0; i<=n; i++) { if((int)g[i].size()>10) { ok=0; } } if(!ok) { printf("Case #%d: 0\n",cas++); continue; } memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1; i<=n; i++) { for(int j=0; j<(1<<(int)g[i-1].size()); j++) { int st=(1<<(int)g[i].size())-1; if(dp[i-1][j]==0) continue; ok=1; for(int k=0; k<(int)g[i-1].size(); k++) { if(j&(1<<k)) { if(v[g[i-1][k]].r<i) { ok=0; } } else { if(v[g[i-1][k]].r>=i) { int now=getnum(g[i-1][k],i); st=bz(st,now); } } } if(ok) { for(int k=0; k<(int)g[i].size(); k++) { if(st&(1<<k)) { dp[i][bz(st,k)]+=dp[i-1][j]; dp[i][bz(st,k)]%=mod; } } dp[i][st]+=dp[i-1][j]; dp[i][st]%=mod; } } } printf("Case #%d: %I64d\n",cas++,f[nn]*dp[n][0]%mod); } return 0; }