noip模拟赛#39
昨晚打开的题想了一会发现都不会后决定慢慢想。然后早上开校会的时候莫名其妙的都想出来了。。。
T1:m=100,ai=50000,i<=5。1到m的数每个数只能用一次,判断是否能够有这些数的某些数相乘得到ai
=>啊O(mai)的dp?利用质因数分解来写?不会。不会。直到早上觉得数据范围那么小写个爆搜+剪枝应该能过吧,毕竟是T1。而且50000的话每个数的因数都挺少的。
正解:
预处理出剩下前k张卡片时对于每个质因数最多可以贡献多少次幂。若搜索到一个状态,就算剩下的卡片所有都可以使用且不考虑分配原则的情况下仍无法约去所有质因数,则必定无解,可以进行剪枝操作。再配合算法一,期望得分100%。
#include<cstdio> #include<cstring> #include<cctype> #include<algorithm> using namespace std; #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define clr(x,c) memset(x,c,sizeof(x)) int read(){ int x=0;char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x; } const int nmax=1e3+5; const int inf=0x7f7f7f7f; int a[10][nmax],t[10],n,m; bool vis[nmax]; bool dfs(int x,int pre,int now){ if(x>n) return 1; int td=t[x]/now; if(td<=a[x][pre]) return 0; if(td<=m&&!vis[td]) { vis[td]=1; if(dfs(x+1,0,1)) return 1; vis[td]=0; } rep(i,pre+1,a[x][0]){ if(td%a[x][i]==0&&!vis[a[x][i]]) { vis[a[x][i]]=1; if(dfs(x,i,now*a[x][i])) return 1; vis[a[x][i]]=0; } } return 0; } int main(){ freopen("cards.in","r",stdin);freopen("cards.out","w",stdout); int T=read(); rep(_T,1,T){ clr(vis,0); n=read(),m=read(); rep(i,1,n) t[i]=read(); rep(i,1,n) a[i][0]=0; rep(i,1,n) rep(j,2,m) if(t[i]%j==0) a[i][++a[i][0]]=j; if(!dfs(1,0,1)) puts("Yes");else puts("No"); } fclose(stdin);fclose(stdout); return 0; } /* 4 2 3 2 3 2 3 3 6 2 5 4 6 2 7 24 30 */
T2:给出一个n*m的矩阵。矩阵中的数表示高度。给出初始水的高度和出水口。求出每个点最后的高度。
=>嗯矩阵这铁定应该是乱搞+bfs。。然后我就想着乱搞啊乱搞啊。。死定了。。然后在一个地方卡住了,有可能有很多条路径可以走啊这bfs的顺序根本没办法保证啊这。。。然后突然发现这不是最短路吗?我的思维能力还是太弱了。。。但是终于想出来了happy。spfa新写法被坑了好久。。
=>裸的spfa只能70分。正解要加个SLF优化。。。不会写。。。
正解:
对于某个点,设T表示流完水后,此点的高度(如果上面没水,就是它本身的高度,否则就是水的高度)。同时,T又可以表示此点到源点的路径上,经过的最小的T的值,毫无疑问,我们就是需要让每个点的T最小。并且,如果某点的T表示的是水的高度,他就可以被他周围的,比他小的T更新。
最后每个点的答案就是他的T减他本身的高度。期望得分70%。
SPFA加入经典的SLF优化,就可以通过100%的数据了。
#include<cstdio> #include<cstring> #include<cctype> #include<algorithm> #include<queue> using namespace std; #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define clr(x,c) memset(x,c,sizeof(x)) int read(){ int x=0;char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x; } const int nmax=805; const int inf=0x7f7f7f7f; int dist[nmax][nmax],a[nmax][nmax],mp[nmax][nmax]; int xx[4]={0,0,1,-1}; int yy[4]={1,-1,0,0}; int q[16777216][2];bool inq[nmax][nmax]; int main(){ freopen("shower.in","r",stdin);freopen("shower.out","w",stdout); int n=read(),m=read(),h=read(); rep(i,1,n) rep(j,1,m) a[i][j]=read(); int sa=read(),sb=read(); rep(i,1,n) rep(j,1,m) mp[i][j]=max(a[i][j]-a[sa][sb],0); if(h<a[sa][sb]) { rep(i,1,n){ rep(j,1,m) printf("%d ",max(h-a[i][j],0));printf("\n"); } return 0; } clr(dist,0x7f);dist[sa][sb]=0; int ql=0,qr=1,x,y,tx,ty,td;q[qr][0]=sa;q[qr][1]=sb; while(ql!=qr){ //这里不能是ql<qr!!!!!!! ql=((ql+1)&1048575);x=q[ql][0];y=q[ql][1];inq[x][y]=0; rep(i,0,3){ tx=x+xx[i];ty=y+yy[i]; if(tx<1||ty<1||tx>n||ty>m) continue; td=max(dist[x][y],mp[tx][ty]); if(dist[tx][ty]>td){ dist[tx][ty]=td; if(!inq[tx][ty]){ inq[tx][ty]=1;qr=((qr+1)&1048575); q[qr][0]=tx;q[qr][1]=ty; } } } } rep(i,1,n){ rep(j,1,m) printf("%d ",dist[i][j]+a[sa][sb]-a[i][j]);printf("\n"); } fclose(stdin);fclose(stdout); return 0; } /* 3 3 66 6 9 1 7 8 1 6 8 1 3 2 */
T3:给出两颗树,在两棵树的任两个点连边可以形成一棵树。求n*m种树的最长路径。
=>很明显应该先求出原树中每个点的最长路径。这个树形dp一下可以解决。然后接下来就是有可能是原树中的路径或是经过新加入的边的路径。那么sort一下然后二分找出那么临界点就可以了。复杂度O(nlogn)。ps:其实nm1e5根本不用sort好不TAT。。。。
正解:
明显地,如果我们确定了i点(不妨设i在N个点的树上),要在另一棵树上找到j点使得新生成的最长链是直径,则只需要满足f(j)>len-1-f(i),如此可以使用桶维护s(x)表示有多少f(j)是小于等于x的,那么确定了i后,f(i)会对答案贡献M-s(len-1-f(i))次,本身两棵树上的较长直径会对答案贡献s(len-1-f(i))次。
对于快速地求出所有的f(i)我们可以选择树形DP后旋根。
在树形DP内我们可以记f(i)为i到以i节点为根的子树下任意节点的最长链。G(i)为与f(i)不相交的次长链。转移的时候只需要把i的所有儿子的f值+1中最大的赋值给f(i),次大的(不需要严格比f(i)小)赋值给g(i)。
然后我们就知道了根节点到树上任意一点的最长链。树上的每个点中MAX{g(i)+f(i)}即是直径。
然后我们需要做的是旋根操作。
如果我们已经知道了根i的f值,并且需要把根转给儿子j。则需要把j对i的f值影响删去再把i当作j的儿子,把i的f值+1与j的f值g值比较。然后就知道了j作为根时的f值与g值(变换后i的g值可能是错误的,但是不影响旋根,所以不需要特地维护)。
就这样遍历整颗树一遍,每个点在其本身的树上可以延伸的最长链f(i)的长度就可以算出来了。
时间复杂度为O(N+M)期望得分100%。
#include<cstdio> #include<cstring> #include<cctype> #include<algorithm> using namespace std; #define rep(i,s,t) for(int i=s;i<=t;i++) #define dwn(i,s,t) for(int i=s;i>=t;i--) #define clr(x,c) memset(x,c,sizeof(x)) #define qwq(x) for(edge *o=head[x];o;o=o->next) #define ll long long int read(){ int x=0;char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x; } const int nmax=2e5+5; const int inf=0x7f7f7f7f; int f[nmax],g[nmax];ll sm[nmax]; struct edge{ int to;edge *next; };edge es[nmax<<1],*pt=es,*head[nmax]; void add(int u,int v){ pt->to=v;pt->next=head[u];head[u]=pt++; pt->to=u;pt->next=head[v];head[v]=pt++; } void dfs(int x,int fa){ qwq(x) if(o->to!=fa){ dfs(o->to,x); f[x]=max(f[x],f[o->to]+1); } } void DFS(int x,int fa){ //printf("(%d %d)\n",x,fa); int ta=-1,tb=-1,ca=0; qwq(x) if(o->to!=fa) { if(f[o->to]>ta) tb=ta,ta=f[o->to],ca=o->to; else if(f[o->to]>tb) tb=f[o->to]; } if(ta!=-1&&tb!=-1) ta+=2,tb+=2; else if(ta<0&&tb<0) ta=0; else tb=0; qwq(x) if(o->to!=fa){ if(o->to!=ca) g[o->to]=max(g[x]+1,ta); else g[o->to]=max(g[x]+1,tb); } qwq(x) if(o->to!=fa) DFS(o->to,x); } int find(int x,int r){ int l=0,ans=0,mid; while(l<=r){ mid=(l+r)>>1; if(f[mid]<=x) ans=mid,l=mid+1; else r=mid-1; } return ans; } //二分的边界什么的总是写错。我开始是弄成g[0]=inf。然后f[mid]>=x 判断。。。然后Wa了三个点。要注意一下。 int main(){ freopen("connect.in","r",stdin);freopen("connect.out","w",stdout); int n=read(),m=read(),u,v; rep(i,1,n-1) u=read(),v=read(),add(u,v); rep(i,1,m-1) u=read()+n,v=read()+n,add(u,v); dfs(1,0);dfs(n+1,0);DFS(1,0);DFS(n+1,0); rep(i,1,n+m) g[i]=max(g[i],f[i]); rep(i,1,m) f[i]=g[i+n]; sort(g+1,g+n+1);sort(f+1,f+m+1); rep(i,1,m) sm[i]=sm[i-1]+f[i]; int mx=max(g[n],f[m]),cur;ll ans=0; rep(i,1,n) { cur=find(mx-g[i]-1,m); ans+=1ll*cur*mx+1ll*(m-cur)*(g[i]+1)+sm[m]-sm[cur]; } printf("%lld\n",ans); fclose(stdin);fclose(stdout); return 0; } /* 3 5 1 2 1 3 1 2 1 3 3 4 4 5 4+8+14=26 */