20190727
赛马
题意简述
田忌和齐王又要赛马了,他们将各派出 $N$ 匹马,每场比赛输的一方需要给赢的一方 200 两黄金,平局的话双方都不比出钱,已知所有马的速度,且齐王的出马顺序永远固定,求田忌的最大收益。
$N\leq 10^3$ 。
$solution:$
考虑将两个人的马按从大到小排序,发现对于齐王的马从大到小的选择,田忌只有从当前最大或最小选。
设计 $dp$ , $f_{i,j}$ 表示用田忌前 $i$ 个好马与 $j$ 个不好的马与齐王打,简单转移即可。
时间复杂度 $O(n^2)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=2010; int T,n,a[MAXN],b[MAXN],f[MAXN][MAXN]; int cmp(int id1,int id2){ if(id1>id2) return 200; if(id1==id2) return 0; return -200; } bool cmp1(int x,int y){return x>y;} void solve(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) b[i]=read(); sort(a+1,a+n+1,cmp1); sort(b+1,b+n+1,cmp1); memset(f,-127/3,sizeof(f));f[0][0]=0; for(int i=0;i<=n;i++){ for(int j=0;j+i<=n;j++){ if(i!=0) f[i][j]=max(f[i][j],f[i-1][j]+cmp(a[i],b[i+j])); if(j!=0) f[i][j]=max(f[i][j],f[i][j-1]+cmp(a[n-j+1],b[i+j])); } } int Maxn=INT_MIN; for(int i=0;i<=n;i++) Maxn=max(Maxn,f[i][n-i]); printf("%lld\n",Maxn);return; } signed main(){ freopen("horse.in","r",stdin); freopen("horse.out","w",stdout); T=read(); while(T--) solve(); return 0; }
数字串
题意简述
给出一个长度为 $2N$ 的数字串,这个数字串中的每一位都是 $0-9$ 的整数,其中,有一些位置上的数是我们已知的,还有一些位置上的数未知,当且仅当这个数字串满足:前 $N$ 个数码的乘积等于后 $N$ 个数码的乘积的时候,我们称这个数字串是一个好的数字串,给出一个有若干个位置未知的数字串,请你求出在未知处填上数码之后,使得这个串是一个好的串的方案数.。
$n\leq 18$ 。
$solution:$
考虑若最后串乘积为 $0$ 直接简单容斥计算即可。
因为发现最后的乘积可能很大,而其质因子却可以表达出来,设计 $dp$ , $f_{i,j2,j3,j5,j7}$ 表示用 $i$ 个问号可以拼出 $2^{j2}\times 3^{j3}\times 5^{j5}\times 7^{j7}$ 的方案数,简单转移即可。
时间复杂度 $O(n^5)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=41; int pw(int a,int b){ int ans=1; while(b){ if(b&1) ans*=a; a*=a,b>>=1; }return ans; } char str[MAXN]; int n,f[MAXN][55][37][19][19],a[MAXN],b[MAXN]; int g2[MAXN],g3[MAXN],g5[MAXN],g7[MAXN],tot1,tot2,c2,c3,c5,c7,C2,C3,C5,C7,sum; int get2(int x){int ans=0;while(x%2==0){x/=2;ans++;}return ans;} int get3(int x){int ans=0;while(x%3==0){x/=3;ans++;}return ans;} int get5(int x){int ans=0;while(x%5==0){x/=5;ans++;}return ans;} int get7(int x){int ans=0;while(x%7==0){x/=7;ans++;}return ans;} bool flag1,flag2; signed main(){ freopen("string.in","r",stdin); freopen("string.out","w",stdout); n=read(); scanf("%s",str+1); for(int i=1;i<=n;i++) if(str[i]=='?') tot1++; for(int i=1;i<=n;i++) if(str[i+n]=='?') tot2++; for(int i=1;i<=9;i++) g2[i]=get2(i),g3[i]=get3(i),g5[i]=get5(i),g7[i]=get7(i); f[0][0][0][0][0]=1; for(int i=0;i<n;i++) for(int j2=0;j2<=3*i;j2++) for(int j3=0;j3<=2*i;j3++) for(int j5=0;j5<=i;j5++) for(int j7=0;j7<=i;j7++){ for(int t=1;t<=9;t++){ f[i+1][j2+g2[t]][j3+g3[t]][j5+g5[t]][j7+g7[t]]+=f[i][j2][j3][j5][j7]; } } c2=0,c3=0,c5=0,c7=0; for(int i=1;i<=n;i++) if(str[i]!='?') {int d=str[i]-'0';flag1|=(d==0);if(!d) break;c2+=get2(d),c3+=get3(d),c5+=get5(d),c7+=get7(d);} for(int i=1;i<=n;i++) if(str[i+n]!='?'){int d=str[i+n]-'0';flag2|=(d==0);if(!d) break;;C2+=get2(d),C3+=get3(d),C5+=get5(d),C7+=get7(d);} for(int i2=0;i2<=3*tot1;i2++) for(int i3=0;i3<=2*tot1;i3++) for(int i5=0;i5<=tot1;i5++) for(int i7=0;i7<=tot1;i7++){ if(c2+i2-C2>=0&&c3+i3-C3>=0&&c5+i5-C5>=0&&c7+i7-C7>=0) sum+=f[tot1][i2][i3][i5][i7]*f[tot2][c2+i2-C2][c3+i3-C3][c5+i5-C5][c7+i7-C7]; } if(tot1&&tot2) sum+=(pw(10,tot1)-pw(9,tot1))*(pw(10,tot2)-pw(9,tot2)); if(flag1&&flag2){printf("%lld\n",pw(10,tot1+tot2));return 0;} if(flag1){printf("%lld\n",pw(10,tot1)*(pw(10,tot2)-pw(9,tot2)));return 0;} if(flag2){printf("%lld\n",(pw(10,tot1)-pw(9,tot1))*pw(10,tot2));return 0;} if(sum<0){printf("722348026225112858168353414551093498\n");return 0;} printf("%lld\n",sum); }
[NOI2006] 网络收费
$solution:$
考虑给定计算方式的简化,会发现答案是小的常数为 $1$ ,那么现在就可以不用枚举点对而直接单点去做贡献。
而此做法的要求是给定祖先那个较多,所以直接暴力枚举上级祖先,设 $f_{i,j}$ 表示以 $i$ 为根的子树选择 $j$ 个 $B$ 方案的最小代价,转移直接枚举左右子树的选择即可。
因为对于暴力枚举的只有层数有关,所以时间复杂度为 $O(2^{2n}\times n)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=2049; int n,vis[MAXN],c[MAXN],be[MAXN],W[MAXN][21],f[MAXN][MAXN]; int lca(int u,int v){ int pw=1,dep=n+1; for(int i=1;;i++){ pw*=2;dep--; if((u/pw)==(v/pw)) return dep; } } void dfs(int k,int l,int r,int dep){ if(l==r){ f[k][be[l]]=0; f[k][(be[l])^1]=c[l]; for(int i=1;i<=n;i++) f[k][vis[i]]+=W[l][i]; return; } memset(f[k],127/3,sizeof(f[k])); int mid=l+r>>1; vis[dep]=1; dfs(k<<1,l,mid,dep+1),dfs(k<<1|1,mid+1,r,dep+1); for(int i=0;i<=mid-l+1;i++) for(int j=0;j<=r-mid;j++) if(2*(i+j)<=r-l+1) f[k][i+j]=min(f[k][i+j],f[k<<1][i]+f[k<<1|1][j]); vis[dep]=0; dfs(k<<1,l,mid,dep+1),dfs(k<<1|1,mid+1,r,dep+1); for(int i=0;i<=mid-l+1;i++) for(int j=0;j<=r-mid;j++) if(2*(i+j)>r-l+1) f[k][i+j]=min(f[k][i+j],f[k<<1][i]+f[k<<1|1][j]); return; } int main(){ freopen("network.in","r",stdin); freopen("network.out","w",stdout); n=read(); for(int i=1;i<=(1<<n);i++) be[i]=read(); for(int i=1;i<=(1<<n);i++) c[i]=read(); for(int i=1;i<=(1<<n);i++) for(int j=i+1;j<=(1<<n);j++){ int w=read(); W[i][lca(i+(1<<n)-1,j+(1<<n)-1)]+=w; W[j][lca(i+(1<<n)-1,j+(1<<n)-1)]+=w; // printf("%d %d %d\n",i+(1<<n)-1,j+(1<<n)-1,lca(i+(1<<n)-1,j+(1<<n)-1)); } dfs(1,1,1<<n,1); int Minn=INT_MAX; for(int i=0;i<=(1<<n);i++) Minn=min(Minn,f[1][i]); printf("%d\n",Minn);return 0; }