910考试题解
时间限制: 1 Sec 内存限制: 128 MB
题解
从看到题的那一刻就开始满脑子“枚举解”“特判”一堆乱七八糟的玩意,然后后来还真特判完了就开始枚举了……然而考了也不知道多长时间以后,看了一眼题,ax+by=c,我为什么只知道题面是个方程没注意是这个方程呢???明显是扩展欧几里德的那个式子……扩欧我还是会打的,但是这道题也教会了我“你只是会打而已”。
尽管是扩欧也分类讨论得很细,这已经不是特判不特判的问题了,而是这题本来需要如此。每组输入时就把a变为非负数。ab都为0,则c=0有无穷解c!=0无解。a=b=1时解的个数就是c-1。a+b=c且b大于0时只有1,1一组解。除去上述情况,b>0时就需要扩欧,求出最小解之后要用到那个我从来没打过的递推原理统计解的个数……b=0时相当于看c=ax有无解,c是a的倍数就有无穷解否则无解。b<0时看c是否是gcd(a,b)的倍数,是则有无穷解否则无解。
后来才想明白的有两个点。一是b<0为何看gcd(a,b)与c的关系,因为这样相当于m*gcd(a,b)-n*gcd(a,b)=c,只要c是gcd(a,b)的倍数就变成了m-n=c/gcd(a,b)的解,这样当然可以m与n同增同减有无穷解,否则就变成了两个整数之差是一个小数,因此无解。二是扩欧之后求解的个数的方法,不定方程的通解是x = x0 + (b/gcd)*t ; y = y0 – (a/gcd)*t,我们一般扩欧求出的是x0,应该据此求y0,因为y是递减的,这样可以根据y是否大于0方便地求出共有多少组解。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int ca,a,b,c,ans,tp,gcd; int e_gcd(int x,int y,int &m,int &n) { if(y==0) { m=1,n=0; return x; } int jg=e_gcd(y,x%y,m,n); int kk=m; m=n; n=kk-x/y*n; return jg; } int ny(int x,int y,int z) { int m,n; gcd=e_gcd(x,y,m,n); if((z%gcd)!=0) return -1; y/=gcd; m*=z/gcd; if(y<0) y=-y; int ans=m%y; if(ans<=0) ans+=y; return ans; } int main() { scanf("%d",&ca); for(int l=1;l<=ca;l++) { scanf("%d%d%d",&a,&b,&c); if(abs(a)<abs(b)) tp=a,a=b,b=tp; if(a<0) a=-a,b=-b,c=-c; if(a==0&&b==0) { if(c==0) printf("ZenMeZheMeDuo\n"); else printf("0\n"); continue; }//both zero if(a==b&&a==1) { if(c<=1) { printf("0\n"); continue; } if(c-1>65535) printf("ZenMeZheMeDuo\n"); else printf("%d\n",c-1); continue; }//a==b==1 if(a+b==c&&b>=0) { printf("1\n"); continue; }//a+b==c&&a>=0&&b>=0 ans=0; if(b>0) { tp=ny(a,b,c); if(tp==-1) { printf("0\n"); continue; } tp=(c-tp*a)/b; if(tp>0) { ans=tp*gcd/a; if(ans*a/gcd!=tp) ans++; } if(ans<=65535) printf("%d\n",ans); else printf("ZenMeZheMeDuo\n"); continue; }//e_gcd if(b==0) { if(c>=0&&(c%a)==0) printf("ZenMeZheMeDuo\n"); else printf("0\n"); }//a>0&&b==0 c=ax if(b<0) { ny(a,b,c); if(c%gcd==0) printf("ZenMeZheMeDuo\n"); else printf("0\n"); } } return 0; }
时间限制: 1 Sec 内存限制: 128 MB
题解
刚开始想了想树dp,感觉这个转移有点奇怪,然后就去想别的思路了。贪心显然没什么道理,最后打了个十分复杂的小范围状压,但是仿佛打挂了,没有拿分。
这个树dp用到了一种很神奇的思路,对于每条边都考虑它对全局的贡献。f[i][j]表示以i为根的树中有j个黑点,则这棵子树以外有m-j个黑点,以此类推白点的个数也能确定出来,这条边对答案的贡献就是可知的。当用子节点求父节点的f时,枚举父节点有多少个黑点,又有多少个在这个子树里,像背包一样转移,转移之后再更新父节点的子树大小,时间复杂度是n^2的(然而我并没有兴趣证明~)。很久没有打过背包了,连枚举的顺序都没注意到,调了半天才想起来还有这回事。好多东西并不是难理解或者容易出错,只是忘掉了最基本的原理。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int sj=2020; int n,m,h[sj],e,fa[sj],a1,a2,a3,size[sj]; long long f[sj][sj],tp; struct B { int ne,v,w; }b[sj<<1]; void add(int x,int y,int z) { b[e].v=y,b[e].ne=h[x],b[e].w=z,h[x]=e++; b[e].v=x,b[e].ne=h[y],b[e].w=z,h[y]=e++; } void bj(long long &x,long long y) { x=x>y?x:y; } void dfs(int x) { size[x]=1; for(int i=h[x];i!=-1;i=b[i].ne) if(b[i].v!=fa[x]) { fa[b[i].v]=x; dfs(b[i].v); a1=size[x]+size[b[i].v]; if(a1>m) a1=m; for(int k=a1;k>=0;k--) { a2=k-size[x]; if(a2<0) a2=0; for(int j=a2;j<=size[b[i].v]&&j<=k;j++) { tp=(long long)((long long)j*(m-j)+(long long)(size[b[i].v]-j)*(n-m-(size[b[i].v]-j)))*b[i].w; bj(f[x][k],f[x][k-j]+f[b[i].v][j]+tp); } } size[x]+=size[b[i].v]; } } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof(h)); for(int i=1;i<n;i++) { scanf("%d%d%d",&a1,&a2,&a3); add(a1,a2,a3); } dfs(1); printf("%lld",f[1][m]); return 0; }