5.31 考试修改+总结
QAQ 迷の卡常数的一天 QAQ
第一题裸的多重背包,果断写单调队列
20min拍上,分析时间复杂度O(T*n*m),感觉根本不虚
然后就被卡常数了,然后出题人丧心病狂把我卡成了60分
(评测机太渣渣了QAQ话说改成3s我就A了啊QAQ)
不过貌似就算A了 我还是rank4
单调队列的做法就不再说了
然后A掉的人有各种鬼畜做法,什么二进制分组维护个链表优化常数。。
对于这道题来说有个性质是背包的重量和价值是一样的
我们只需要看前i个物品能否完成拼出j的重量就可以了,这样做bool变量的DP
同时可以去掉单调队列,只需要知道可转移的区间中是否有true就可以了QAQ随便拿个cnt都能记录
然后这样常数就小了很多(虽然时间复杂度还是一样的,鉴于出题人就是想卡常数,所以。。)
贴单调队列的代码QAQ
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #define fastcall __attribute__((optimize("-O3"))) #define IL __inline__ __attribute__((always_inline)) using namespace std; typedef long long LL; const int maxn=310; int T,n,m,mx; int L,R,k; struct ice{ int a,b; }c[maxn]; int h,t; struct Que{ int mul,id; }Q[10010]; int f[310][10010]; int ans[310][10010]; fastcall IL void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } fastcall IL int Max(int a,int b){return a>b?a:b;} //f[i][j]=max(f[i-1][j-Num*w]+Num*v) int main(){ read(T); while(T--){ read(n);read(m); for(int i=1;i<=n;++i)read(c[i].a),read(c[i].b); for(int i=0;i<=10000;++i)f[0][i]=0; for(int i=1;i<=n;++i){ int w=c[i].a,lim=c[i].b; for(int j=0;j<w;++j){ int cnt=0;f[i][j]=f[i-1][j]; h=t=1;Q[t].mul=cnt;Q[t].id=j; for(int k=j+w;k<=10000;k+=w){ cnt++; while(h<=t&&f[i-1][k]-cnt*w>=f[i-1][Q[t].id]-Q[t].mul*w)t--; ++t;Q[t].mul=cnt;Q[t].id=k; while(h<=t&&cnt-Q[h].mul>lim)h++; f[i][k]=f[i-1][Q[h].id]+(cnt-Q[h].mul)*w; } } } for(int i=1;i<=n;++i){ for(int j=1;j<=10000;++j){ ans[i][j]=ans[i][j-1]+f[i][j]; } } while(m--){ read(L);read(R);read(k); printf("%d\n",ans[k][R]-ans[k][L-1]); } }return 0; }
第二题
先SPFA搞出每个点可能的最大电势(就是最短路)
然后可转移的点连边,显然是个拓扑图,由于每个自爆怪都是独立的
搞出每个点的sg值就可以了,随便拓扑排序或者记忆化都可以
最后所有怪异或起来就是答案
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; const int maxn=20010; const int oo=0x7fffffff/3; int T,n,m,k; int u,v,w; int h[maxn],cnt=0; int tot=0; int dis[maxn]; int deg[maxn]; int lis[maxn],sum=0; int sg[maxn],check[maxn],tim=0; bool vis[maxn]; struct edge{ int to,next,w; }G[500010]; struct Edge{ int u,v; }c[100010]; queue<int>Q; void add(int x,int y,int z=0){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; } void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void SPFA(){ dis[1]=0;vis[1]=true;Q.push(1); for(int i=2;i<=n;++i)dis[i]=oo,vis[i]=false; while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(dis[v]>dis[u]+G[i].w){ dis[v]=dis[u]+G[i].w; if(!vis[v])vis[v]=true,Q.push(v); } }vis[u]=false; }return; } void build_Graph(){ tot=0; for(int u=1;u<=n;++u){ for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(dis[v]>dis[u]){ ++tot; c[tot].u=u;c[tot].v=v; } } } memset(deg,0,sizeof(deg)); memset(h,0,sizeof(h));cnt=0; for(int i=1;i<=tot;++i)add(c[i].u,c[i].v),deg[c[i].v]++; } void toposort(){ sum=0; for(int i=1;i<=n;++i)if(!deg[i])Q.push(i); while(!Q.empty()){ int u=Q.front();Q.pop(); lis[++sum]=u; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; --deg[v]; if(!deg[v])Q.push(v); } }return; } void Get_SG(){ for(int i=sum;i>=1;--i){ int now=lis[i];tim++; bool flag=true; for(int j=h[now];j;j=G[j].next){ int v=G[j].to; check[sg[v]]=tim; flag=false; } if(flag){sg[now]=1;continue;} for(int j=0;j!=-1;j++){ if(check[j]!=tim){ sg[now]=j; break; } } }return; } int main(){ while(scanf("%d%d%d",&n,&m,&k)==3){ memset(h,0,sizeof(h));cnt=0; for(int i=1;i<=m;++i){ read(u);read(v);read(w); add(u,v,w);add(v,u,w); } SPFA();build_Graph(); toposort();Get_SG(); int ans=0; while(k--){ read(u); ans^=sg[u]; } if(!ans)printf("Other\n"); else printf("QQ\n"); }return 0; }
第三题考场上丝薄了,没有想出来
原因是没有仔细观察模数的性质,首先如果模数很小我们维护一颗线段树每次枚举进入的数是多少完成区间合并就可以了
所以当模数是17的时候非常好做
然后给的这个模数非常的鬼畜,我一直以为是个质数,所以百思不得其解
他实际上是11*13*17*19
那么做法就非常显然了,你对于这四个模数分别维护线段树,最后得到四个答案CRT即可
出题人给了个5000的暴力范围完全不知道出题人在想些什么(难道乘方操作可以预处理省个log?)
这个题目我们把模数扩大而且是个质数,但是减掉一些操作我们还是可以做的:
1、去掉乘方,我们把每个区间用kx+b来表示,维护线段树或者平衡树即可
2、去掉加、减,我们对模数搞出原根,之后对所有的操作取模意义下的log,最后快速幂回去即可
但是至今想不出来如果什么都不去怎么做QAQ
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; typedef long long LL; const int maxn=100010; int n,m,type,x,a; char b; char ch[maxn]; int Num[maxn]; int M[4]; int pow_mod(int v,LL p,int mod){ int tmp=1; while(p){ if(p&1)tmp=tmp*v%mod; v=v*v%mod;p>>=1; }return tmp; } struct Seg_Tree{ int ans[maxn<<2][20]; void build(int o,int L,int R,int mod){ if(L==R){ for(int i=0;i<mod;++i){ if(ch[L]=='+'){ ans[o][i]=i+Num[L];ans[o][i]%=mod; }else if(ch[L]=='-'){ ans[o][i]=i-Num[L];ans[o][i]%=mod; if(ans[o][i]<0)ans[o][i]+=mod; }else if(ch[L]=='*'){ ans[o][i]=1LL*i*Num[L]%mod; }else{ ans[o][i]=pow_mod(i,Num[L],mod); } }return; } int mid=(L+R)>>1; build(o<<1,L,mid,mod); build(o<<1|1,mid+1,R,mod); int l=(o<<1),r=(o<<1|1); for(int i=0;i<mod;++i)ans[o][i]=ans[r][ans[l][i]]; } void modify(int o,int L,int R,int p,int mod){ if(L==R){ for(int i=0;i<mod;++i){ if(ch[L]=='+'){ ans[o][i]=i+Num[L];ans[o][i]%=mod; }else if(ch[L]=='-'){ ans[o][i]=i-Num[L];ans[o][i]%=mod; if(ans[o][i]<0)ans[o][i]+=mod; }else if(ch[L]=='*'){ ans[o][i]=1LL*i*Num[L]%mod; }else{ ans[o][i]=pow_mod(i,Num[L],mod); } }return; } int mid=(L+R)>>1; if(p<=mid)modify(o<<1,L,mid,p,mod); else modify(o<<1|1,mid+1,R,p,mod); int l=(o<<1),r=(o<<1|1); for(int i=0;i<mod;++i)ans[o][i]=ans[r][ans[l][i]]; } }T[4]; char s[17][12]={"Fight","Flying","Poison","Ground","Rock","Bug","Ghost","Steel","Fire","Water","Grass","Electric","Psychic","Ice","Dragon","Dark","Fairy"}; int CRT(int a,int b,int c,int d){ return (a*29393ll+b*35530ll+c*29887ll+d*43758ll)%46189ll; } int main(){ scanf("%d%d",&n,&m); M[0]=11;M[1]=13;M[2]=17;M[3]=19; for(int i=1;i<=n;++i){ ch[i]=getchar(); while(ch[i]<'!')ch[i]=getchar(); scanf("%d",&Num[i]); } for(int i=0;i<4;++i)T[i].build(1,1,n,M[i]); while(m--){ scanf("%d",&type); if(type==1){ scanf("%d",&a); int A=T[0].ans[1][a%11]; int B=T[1].ans[1][a%13]; int C=T[2].ans[1][a%17]; int D=T[3].ans[1][a%19]; printf("%s ",s[C]); printf("%d\n",CRT(A,B,C,D)); }else{ scanf("%d",&x); b=getchar(); while(b<'!')b=getchar(); scanf("%d",&a); ch[x]=b;Num[x]=a; for(int i=0;i<4;++i)T[i].modify(1,1,n,x,M[i]); } }return 0; }
今天考试总结:
1、没有想到oj的评测机那么慢QAQ(理论上6000w的复杂度要跑3s)
2、对于模数的一类关于CRT的解题方式不敏感(或者根本就不知道)
3、缺少对于模数的观察,想当然以为模数是个质数
如果考场能看出模数是若干很小质数的乘积的话就很容易得到思路了
一开始总想着用什么数据结构直接维护,结果就只能写50分的暴力QAQ
思考的时候一直没有尝试着转换思路,而是转换数据结构
思路闭塞也是自己的一大弱点,貌似最近被压rank都是自己有一道题目A不掉?上面的人都A掉了QAQ
留下的坑:CRT相关练习
还有一些无关算法的坑:进军TC QAQ 学习emacs(不过是回衡中以后的事情啦)