10月12日考试 题解(数位DP+递归+二分图+背包)
题目难度与题目顺序没有一点关系……
T1 num
原题:CF55D
题目大意:求$[l,r]$内有多少数满足各数位上的数都能整除原数。$r\leq 10^{18}$
显然数位DP。可以发现,如果几个数的最小公倍数能整除原数,那么这几个数也一定能够整除原数。所以我们不妨从$1$到$9$的最小公倍数入手。因为题目要求的是最后看这个数能不能满足要求,所以我们在记搜时只用考虑在模$1$到$9$的最小公倍数意义下的值就可以了。同时还要记录一个当前的最小公倍数;发现$20\times 2520\times 2520$开不下,不妨离散化$2520$的因数(不超过$50$个)。于是这道题被我们解决了。
时间复杂度$O(metaphisics)$
代码:
#include<cstdio> #include<iostream> #include<cstring> #define int long long using namespace std; const int p=2520; int f[20][2551][55],a[20],num[2551],T,l,r,len,cnt; inline int gcd(int x,int y){ return (y==0)?x:gcd(y,x%y); } inline int LCM(int x,int y){ return x*y/gcd(x,y); } inline void init() { memset(f,-1,sizeof(f)); cnt=0; for (int i=1;i<=p;i++) if (p%i==0) cnt++,num[i]=cnt; } inline int dfs(int pos,int sum,int lcm,int limit) { if (!pos) return sum%lcm==0; if (!limit&&f[pos][sum][num[lcm]]!=-1) return f[pos][sum][num[lcm]]; int res=limit?a[pos]:9,ret=0; for (int i=0;i<=res;i++) { int nxt_sum=(sum*10+i)%p; int nxt_lcm=lcm; if (i) nxt_lcm=LCM(nxt_lcm,i); ret+=dfs(pos-1,nxt_sum,nxt_lcm,limit&&i==res); } if (!limit) f[pos][sum][num[lcm]]=ret; return ret; } inline int solve(int x) { len=0; while(x) a[++len]=x%10,x/=10; return dfs(len,0,1,1); } signed main() { init(); scanf("%lld",&T); while(T--) { scanf("%lld%lld",&l,&r); printf("%lld\n",solve(r)-solve(l-1)); } return 0; }
T2 rps
题目大意:有$n$个人玩石头剪刀布,给定每个人一直出的拳。赢的进入下一轮。要求找出字典序最小的方案使得游戏能够结束。如果没有方案输出$-1$。
知道每个人出的拳之后就可以知道谁赢了。晋级关系构成一棵树,不妨枚举树根最后是谁赢了,然后递归暴力比较字典序即可。
时间复杂度$O(n)$。
代码:
#include<bits/stdc++.h> using namespace std; int r[3],n=0; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } char s[]="RPS"; string solve(int x,int y){ if(!x){ string ans; ans.push_back(s[y]); return ans; } string a=solve(x-1,y),b=solve(x-1,(y+1)%3); return a<b?a+b:b+a; } bool judge(int x){ int a[3]={},aa[3]; a[x]=1; for(int i=1;i<=n;i++){ for(int j=0;j<=2;j++) aa[j]=a[j]; for(int j=0;j<=2;j++) a[(j+1)%3]+=aa[j]; } for(int i=0;i<=2;i++) if(a[i]!=r[i]) return 0; cout<<solve(n,x)<<endl; return 1; } int main(){ r[0]=read(),r[1]=read(),r[2]=read(); while((1<<n)!=r[0]+r[1]+r[2]) n++; int i=0; for(;i<=2;i++){ if(judge(i)==1) break; } if(i>2) cout<<"IMPOSSIBLE"<<endl; return 0; }
T3 导弹拦截
题目大意:给定$n$个三元组$(x,y,z)$,表示导弹打的坐标;一个拦截系统拦截一个导弹后只能拦截$x,y,z$均比其小的导弹。问一次性最多拦截的导弹个数和所需要的最少的拦截系统。
第一问很水,直接最长上升子序列搞就行了。第二问我们可以抽象成一张有向无环图,边代表一个导弹对另一个导弹的“控制”(指$x,y,z$大小关系)。这时题意转变成求最小路径覆盖。4月份学二分图的时候博客里记了结论$ans=n-tot$($tot$指最大匹配),但没有证明。简单说一下:
一开始$n$个点看成$n$条不相交路径,没增加一个匹配等于将两个路径连到一起,路径数减少$1$。所以最少的路径数为$n-tot$。
时间复杂度$O(n^2)$。
代码:
#include<cstdio> #include<iostream> #include<vector> #include<cstring> #include<algorithm> using namespace std; const int N=1005; int n,f[N],vis[N],c[N],ans,tot; vector<int> v[N]; struct node{ int x,y,z; }a[N]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline bool cmp(node a,node b) { return a.x<b.x; } inline int dfs(int x) { for (int i=0;i<v[x].size();i++) { int to=v[x][i]; if (!vis[to]) { vis[to]=1; if (!c[to]||dfs(c[to])){ c[to]=x; return 1; } } } return 0; } int main() { n=read(); for (int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].z=read(); for (int i=1;i<=n;i++) { f[i]=1; for (int j=0;j<i;j++) { if (a[i].x>a[j].x&&a[i].y>a[j].y&&a[i].z>a[j].z) v[j].push_back(i),vis[i]=1; else if (a[j].x>a[i].x&&a[j].y>a[i].y&&a[j].z>a[i].z) v[i].push_back(j),vis[j]=1; } } sort(a+1,a+n+1,cmp); for (int i=1;i<=n;i++) for (int j=0;j<i;j++) if (a[i].x>a[j].x&&a[i].y>a[j].y&&a[i].z>a[j].z) f[i]=max(f[i],f[j]+1); for (int i=1;i<=n;i++) ans=max(ans,f[i]); printf("%d\n",ans); for (int i=1;i<=n;i++) if (v[i].size()){ memset(vis,0,sizeof(vis)); tot+=dfs(i); } printf("%d",n-tot); return 0; }
T4 符文师
题目大意:给定一个长度为$n$的序列,每个数都有一个值$d_i$和$l_i$,表示杀伤力和其后面不能选的数的个数。有两种操作:1.将第一个数放到最后;2.选这个数,连带着后面$l_i$个数都消掉。问最大杀伤力。
傻逼题。发现一个数的消去其实对于你想要的答案是没影响的,等于说现在有$n$个物品,体积为$l_i$,价值为$d_i$,你有一个大小为$n$的背包,然后求最大价值。直接01背包就完事了。
时间复杂度$O(n^2)$。
代码:
#include<cstdio> #include<iostream> using namespace std; const int N=1005; int f[N],w[N],v[N],n; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&w[i]); for (int i=1;i<=n;i++) scanf("%d",&v[i]); for (int i=1;i<=n;i++) for (int j=n;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); printf("%d",f[n]); return 0; }