Day1
T1生活大爆炸版剪刀石头布:模拟,水;
T2联合权值:树形DP,水;
T3Flappy Birds:
这道题我当时算时间复杂度算错了,O(nm^2)的时间复杂度给算成O(nm)了,所以根本就没想优化,以后①算时间复杂度的时候要小心一点了。
其实正解也是很简单的,只是在直译式DP的基础上做了一点小优化。
直译式DP:设f(i,j)为到达当前点的最小步数,则
f(i,j)->f(i-1,j+Y[i-1])
->f(i-1,j-k*X[i-1])+k,k>0且j-k*X[i-1]>0
完全背包式优化:
f(i,j)->f(i-1,j-X[i-1])+1
->f(i,j-X[i-1])+1
->f(i-1,j+Y[i-1])
(顺序不能颠倒)
“因为f(i,j)这个状态实际上可以由f(i,j-X[i-1])这个状态转移而来"——GTY orz
②这种多步与一步等价的思想实际上是非常实用和需要我掌握的。
回来以后写的AC代码:
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> typedef short hd; char * ptr=(char *)malloc(1000000); int f[10001][1001]; inline void in(hd &x){ while(*ptr<'0'||*ptr>'9')++ptr; x=0; while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0'; } int main(){ hd P[10000],down[10001],up[10001],X[10000],Y[10000],N,M,K,tmp,i,j; int MAXN=2000000000; freopen("birda.in","r",stdin); freopen("birda.out","w",stdout); //读入 fread(ptr,1,1000000,stdin); in(N),in(M),in(K); for(i=0;i<N;++i)in(X[i]),in(Y[i]); for(i=0;i<=N;++i)up[i]=M+1; for(i=0;i<K;++i)in(P[i]),in(down[P[i]]),in(up[P[i]]); //初始化 sort(P,P+K); memset(*f+1001,127,sizeof(f)-sizeof(int)*1001); f[0][0]=MAXN; //DP for(i=1;i<=N;++i){ for(j=1;tmp=min((hd)(j+X[i-1]),M),j<=M;++j)f[i][tmp]=min(f[i][tmp],f[i-1][j]+1); for(j=1;tmp=min((hd)(j+X[i-1]),M),j<M;++j)f[i][tmp]=min(f[i][tmp],f[i][j]+1); for(j=M;j>Y[i-1];--j)f[i][j-Y[i-1]]=min(f[i][j-Y[i-1]],f[i-1][j]); for(j=0;j<=down[i];++j)f[i][j]=MAXN; for(j=up[i];j<=M;++j)f[i][j]=MAXN; //判断当前列是否可达 j=1; while(j<=M&&f[i][j]>=MAXN)++j; if(j>M)break; } if(i>N)printf("1\n%d",*min_element(f[N]+1,f[N+1])); else printf("0\n%d",lower_bound(P,P+K,i)-P); }
Day2
T1无线网络发射器选址:当时蛋疼写了个DP,实际上暴力就可以,水。
T2寻找道路:我发现D2好像一直很蛋疼,又写了个DFS+SPFA,实际上DFS+BFS就可以了。
T3:解方程:
这是一道很奇怪的题,我刚看到他的时候没有一点思路。。除了暴力。然后就想写个50分的压位高精暴力,结果还写残了。。所以只得了30分。回来的路上听到学长们讨论各种取模+乱搞。直接取模+暴力的话,O(nm)的时间复杂度实际上是可以得70分的。
那么就来说一下正确解法吧。——来自,各种大神的题解。
引理:若f(x)≡0(mod p),则f(x+p)≡0(mod p).
所以,我们可以先取一个小质数P,求出[1,P)内所有的解,然后可以通过+P推得在[1,m]内所有符合f(x)≡0(mod P),再用另一个大质数p,用来check每个解即可。
时间复杂度的话,应该是难以估计的,因为模质数本身就是一个用正确率换时间的做法嘛。。对于普通一点的数据的话,应该是可以达到O(n*p+n*n*m/p)。我们发现这玩意儿竟然是我刚学的对勾函数。。当p最接近(n*m)^(1/2)时,时间复杂度最低。此时O(10^2*10^4+10^4*10^6/10^4)≈O(2*10^6).
代码:
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> int main(){ const int P=10007,p=100000009; int i,j,m,n,a[101]={0},b[101]={0},mi[101],ans[101],tmp,lst[10008],h=0,t=0; ans[0]=1,mi[0]=1; freopen("equationa.in","r",stdin); freopen("equationa.out","w",stdout); char * ptr=(char *)malloc(2000000); scanf("%d%d",&n,&m); fread(ptr,1,2000000,stdin); for(i=0;i<=n;++i){ bool flag=0; while(*ptr<'0'||*ptr>'9') if(*ptr++=='-') flag=1; while(*ptr>47&&*ptr<58){ a[i]=(a[i]*10+*ptr-'0')%P; b[i]=(b[i]*10+*ptr++-'0')%p; } if(flag){ a[i]=-a[i]; b[i]=-b[i]; } } for(i=1;i<P;++i){ for(j=1;j<=n;++j)mi[j]=((long long)mi[j-1]*i)%P; tmp=0; for(j=0;j<=n;++j)tmp=(tmp+(long long)mi[j]*a[j])%P; if(!tmp)lst[++t]=i; } do{ h%=P; //check for(j=1;j<=n;++j)mi[j]=((long long)mi[j-1]*lst[h])%p; tmp=0; for(j=0;j<=n;++j)tmp=(tmp+(long long)mi[j]*b[j])%p; if(!tmp)ans[ans[0]++]=lst[h]; //扩展新节点 if(lst[h]+P<=m)lst[t=(t+1)%P]=lst[h]+P; }while(h++!=t); printf("%d",ans[0]-1); for(i=1;i<ans[0];++i)printf("\n%d",ans[i]); }
③这道题应该说让我对hash有了全新的认识。其优化暴力的方法也是很巧妙的,将其花费n*p的时间复杂度模一个小质数之后,我们会发现我们可以判定在(p,m]的范围内有些值是必定不可取的了,即若f(x)≠0(mod p),f(x+p)≠0(mod p),这样便达到了对暴力的优化。