zoukankan      html  css  js  c++  java
  • POI2015题解

    POI2015题解

    吐槽一下为什么POI2015开始就成了破烂波兰文题目名了啊。。。

    咕了一道3748没写打表题没什么意思,还剩(BZOJ)上的(14)道题。

    [BZOJ3746][POI2015]Czarnoksiężnicy okrągłego stołu

    这个题真的神仙。

    (p=0),答案是([n=1])

    (p=1),答案是([n=1]+[n=2,k=0])

    (p=2),只有至多两种方案,即(n)左手边坐(n-1,n-3...),右手边坐(n-2,n-4...)或是反过来。分别判一下是否满足限制即可。

    (p=3)

    考虑增量构造,即按编号从大到小加入。当我们加入(x)时,它当前的左右两边一定要是(x+1,x+2,x+3)三者中的其二,否则将会不合法。可以发现,我们关心的只有(x+1,x+2,x+3)这三个数两两之间是否直接相邻以及它们的相对顺序(顺/逆时针排列),所以设状态(f_{i,s,j})表示填了(i...n)(i,i+1,i+2)的排列方式是顺时针还是逆时针,(i)(i+1)(i)(i+2)(i+1)(i+2)之间分别有没有大于(i+2)的数(也就是是否直接相邻)。

    转移的时候只有至多三种插入方式,分别讨论一下是否合法即可。注意最后(i=1)时要特判(i,i+1,i+2)之间是否合法。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    const int mod = 1e9+7;
    int n,k,p,f[N][2][8],a[N],b[N][7];
    inline bool chk(int x,int y){
    	if (abs(x-y)>p) return false;
    	return !b[x][y-x+3];
    }
    inline bool judge(int x,int s,int j,int k){
    	int s1=j>>2,s2=j>>1&1,s3=j&1,fg=1;
    	if (!s)
    		if (!k) fg&=(!s1)&(s2|chk(x+3,x+1))&(s3|chk(x+2,x+3));
    		else if (k&1) fg&=(!s2)&(s3|chk(x+2,x+3))&chk(x+3,x);
    		else fg&=(!s3)&(s2|chk(x+3,x+1))&chk(x,x+3);
    	else
    		if (!k) fg&=(!s1)&(s2|chk(x+1,x+3))&(s3|chk(x+3,x+2));
    		else if (k&1) fg&=(!s2)&(s3|chk(x+3,x+2))&chk(x,x+3);
    		else fg&=(!s3)&(s2|chk(x+1,x+3))&chk(x+3,x);
    	if (x==1)
    		if (!s)
    			if (!k) fg&=chk(x+1,x)&chk(x,x+2);
    			else if (k&1) fg&=chk(x,x+1)&(s1|chk(x+1,x+2));
    			else fg&=chk(x+2,x)&(s1|chk(x+1,x+2));
    		else
    			if (!k) fg&=chk(x,x+1)&chk(x+2,x);
    			else if (k&1) fg&=chk(x+1,x)&(s1|chk(x+2,x+1));
    			else fg&=chk(x,x+2)&(s1|chk(x+2,x+1));
    	return fg;
    }
    inline void upt(int &x,int y){x+=y;x>=mod?x-=mod:x;}
    int main(){
    	n=gi();k=gi();p=gi();
    	for (int i=1;i<=k;++i){
    		int x=gi(),y=gi();
    		if (abs(x-y)>3) continue;
    		b[x][y-x+3]=1;
    	}
    	if (p==0) return puts(n==1?"1":"0"),0;
    	if (p==1) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
    	if (p==2){
    		if (n<=2) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
    		int fg=1,ans=0;a[1]=n;
    		for (int i=2,j=n-1;j>0;++i,j-=2) a[i]=j;
    		for (int i=n,j=n-2;j>0;--i,j-=2) a[i]=j;
    		for (int i=1;i<n;++i) fg&=chk(a[i],a[i+1]);fg&=chk(a[n],a[1]);
    		ans+=fg;fg=1;
    		for (int i=2,j=n-2;j>0;++i,j-=2) a[i]=j;
    		for (int i=n,j=n-1;j>0;--i,j-=2) a[i]=j;
    		for (int i=1;i<n;++i) fg&=chk(a[i],a[i+1]);fg&=chk(a[n],a[1]);
    		ans+=fg;printf("%d
    ",ans);
    	}
    	if (p==3){
    		if (n<=2) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
    		f[n-2][0][0]=f[n-2][1][0]=1;
    		for (int i=n-2;i>1;--i)
    			for (int s=0;s<2;++s)
    				for (int j=0;j<8;++j)
    					if (f[i][s][j]){
    						if (judge(i-1,s,j,0)) upt(f[i-1][s^1][1],f[i][s][j]);
    						if (judge(i-1,s,j,1)) upt(f[i-1][s][2|j>>2],f[i][s][j]);
    						if (judge(i-1,s,j,2)) upt(f[i-1][s][4|j>>2],f[i][s][j]);
    					}
    		int ans=0;
    		for (int s=0;s<2;++s)
    			for (int j=0;j<8;++j)
    				upt(ans,f[1][s][j]);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    [BZOJ3747][POI2015]Kinoman

    从左到右枚举右端点,维护左端点的答案。

    需要支持的操作是区间加和区间取最大值。用线段树维护即可。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    int n,m,a[N],lst[N],pos[N],b[N];long long mx[N<<2],tag[N<<2],ans;
    void modify(int x,int l,int r,int ql,int qr,int v){
    	if (l>=ql&&r<=qr) {mx[x]+=v;tag[x]+=v;return;}
    	int mid=l+r>>1;
    	if (ql<=mid) modify(x<<1,l,mid,ql,qr,v);
    	if (qr>mid) modify(x<<1|1,mid+1,r,ql,qr,v);
    	mx[x]=max(mx[x<<1],mx[x<<1|1])+tag[x];
    }
    int main(){
    	n=gi();m=gi();
    	for (int i=1;i<=n;++i) lst[i]=pos[a[i]=gi()],pos[a[i]]=i;
    	for (int i=1;i<=m;++i) b[i]=gi();
    	for (int i=1;i<=n;++i){
    		modify(1,1,n,1,i,b[a[i]]);
    		if (lst[i]){
    			if (lst[lst[i]]) modify(1,1,n,1,lst[lst[i]],b[a[i]]);
    			modify(1,1,n,1,lst[i],-b[a[i]]<<1);
    		}
    		ans=max(ans,mx[1]);
    	}
    	printf("%lld
    ",ans);return 0;
    }
    

    [BZOJ3749][POI2015]Łasuchy

    对每盘食物设一个状态(S={0,1,2,3})表示是否每相邻的两个人吃,然后就可以大力(dp)了。

    枚举起点,(dp)一圈回来判一下是否合法。记录前驱输出方案即可。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    int n,a[N],S,f[N][4],g[N][4],b[N];
    bool dp(){
    	memset(f,0,sizeof(f));f[0][S]=1;
    	for (int i=1;i<=n;++i){
    		if (f[i-1][1]&&a[i-1]>=a[i]) f[i][0]=1,g[i][0]=1;
    		else if (f[i-1][3]&&a[i-1]>=2*a[i]) f[i][0]=1,g[i][0]=3;
    
    		if (f[i-1][1]&&2*a[i-1]>=a[i]) f[i][1]=1,g[i][1]=1;
    		else if (f[i-1][3]&&a[i-1]>=a[i]) f[i][1]=1,g[i][1]=3;
    
    		if (f[i-1][0]&&a[i]>=a[i-1]) f[i][2]=1,g[i][2]=0;
    		else if (f[i-1][2]&&2*a[i]>=a[i-1]) f[i][2]=1,g[i][2]=2;
    
    		if (f[i-1][0]&&a[i]>=2*a[i-1]) f[i][3]=1,g[i][3]=0;
    		else if (f[i-1][2]&&a[i]>=a[i-1]) f[i][3]=1,g[i][3]=2;
    	}
    	return f[n][S];
    }
    int main(){
    	n=gi();
    	for (int i=1;i<=n;++i) a[i]=gi();a[0]=a[n];
    	for (S=0;S<4;++S) if (dp()) break;
    	if (S==4) return puts("NIE"),0;
    	for (int i=n;i;--i){
    		if (S&1) b[i]=i;
    		if (S&2) b[i-1?i-1:n]=i;
    		S=g[i][S];
    	}
    	for (int i=1;i<=n;++i) printf("%d ",b[i]);
    	puts("");return 0;
    }
    

    [BZOJ3750][POI2015]Pieczęć

    确定最左上的那个点,直接染色就好了。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1005;
    int n,m,a,b,t,f,g[N][N],dx[N*N],dy[N*N];char s[N];
    bool cover(int p,int q){
    	for (int i=1;i<=t;++i){
    		int x=p+dx[i],y=q+dy[i];
    		if (x<1||x>n||y<1||y>m||!g[x][y]) return false;
    		g[x][y]=0;
    	}
    	return true;
    }
    int main(){
    	int Case=gi();while (Case--){
    		n=gi();m=gi();a=gi();b=gi();t=0;f=1;
    		for (int i=1;i<=n;++i){
    			scanf("%s",s+1);
    			for (int j=1;j<=m;++j) g[i][j]=s[j]=='x';
    		}
    		for (int i=1;i<=a;++i){
    			scanf("%s",s+1);
    			for (int j=1;j<=b;++j)
    				if (s[j]=='x') dx[++t]=i,dy[t]=j;
    		}
    		for (int i=2;i<=t;++i) dx[i]-=dx[1],dy[i]-=dy[1];
    		dx[1]=dy[1]=0;
    		for (int i=1;i<=n;++i)
    			for (int j=1;j<=m;++j)
    				if (g[i][j]&&!cover(i,j)) {f=0;break;}
    		puts(f?"TAK":"NIE");
    	}
    	return 0;
    }
    

    [BZOJ4377][POI2015]Kurs szybkiego czytania

    设匹配位置的第一个下标位置为(x)

    那么串(s)相当于给出限制:(a(x+i)+bmod nge p)或是(a(x+i)+bmod n< p)

    于是就可以确定(ax)这个量在([0,n))内的取值范围,或者说是不能取到的范围

    所以拿个扫描线维护一下这个东西就好了。

    注意最后(m-1)个位置是无论如何不能匹配的,所以还要特殊处理一下。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    int n,a,b,p,m,cnt,ans;char s[N];
    struct node{
    	int x,y;
    	bool operator < (const node &b) const
    		{return x<b.x;}
    }t[N<<3];
    void cover(int l,int r){
    	t[++cnt]=(node){l,1};t[++cnt]=(node){r+1,-1};
    }
    int main(){
    	n=gi();a=gi();b=gi();p=gi();m=gi();scanf("%s",s+1);
    	for (int i=1,x=b;i<=m;++i,(x+=a)%=n)
    		if (s[i]-'0')
    			if (x<p) cover(n-x,n-1),cover(0,p-x-1);
    			else cover(n-x,p+n-x-1);
    		else
    			if (x<=p) cover(p-x,n-x-1);
    			else cover(0,n-x-1),cover(p+n-x,n-1);
    	for (int i=n-m+1;i<n;++i) cover(1ll*a*i%n,1ll*a*i%n);
    	sort(t+1,t+cnt+1);t[++cnt]=(node){n,0};
    	for (int i=1,s=0;i<=cnt;++i){
    		if (t[i].x!=t[i-1].x)
    			if (!s) ans+=t[i].x-t[i-1].x;
    		s+=t[i].y;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    [BZOJ4378][POI2015]Logistyka

    对于每次询问,小于等于(s)的数可以被减成零,而大于(s)的数至多减(s),所以就只需要比较(c imes s)与小于等于(s)的数之和(+)大于(s)的数的个数( imes s)这两个数大小就行了。用两个(BIT)维护。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define ll long long
    const int N = 1e6+5;
    int n,m,op[N],a[N],b[N],o[N],len,val[N],c1[N];ll c2[N];
    
    void mdf1(int k,int v){while(k<=len)c1[k]+=v,k+=k&-k;}
    int qry1(int k){int s=0;while(k)s+=c1[k],k^=k&-k;return s;}
    void mdf2(int k,int v){while(k<=len)c2[k]+=v,k+=k&-k;}
    ll qry2(int k){ll s=0;while(k)s+=c2[k],k^=k&-k;return s;}
    
    void add(int x){mdf1(x,1),mdf2(x,o[x]);}
    void del(int x){mdf1(x,-1),mdf2(x,-o[x]);}
    
    int main(){
    	n=gi();m=gi();
    	for (int i=1;i<=m;++i) op[i]=getchar()=='U',a[i]=gi(),o[++len]=b[i]=gi();
    	o[++len]=0;sort(o+1,o+len+1);len=unique(o+1,o+len+1)-o-1;
    	for (int i=1;i<=m;++i) b[i]=lower_bound(o+1,o+len+1,b[i])-o;
    	mdf1(1,n);
    	for (int i=1;i<=n;++i) val[i]=1;
    	for (int i=1;i<=m;++i)
    		if (op[i]) del(val[a[i]]),val[a[i]]=b[i],add(val[a[i]]);
    		else puts(1ll*(qry1(len)-qry1(b[i]))*o[b[i]]+qry2(b[i])>=1ll*a[i]*o[b[i]]?"TAK":"NIE");
    	return 0;
    }
    

    [BZOJ4379][POI2015]Modernizacja autostrady

    毒瘤warning!

    换根(dp)求出切断每条边之后两棵树内的直径。需要维护:子树内从(u)出发的前三长链,子树内最大次大直径(不算当前点),子树最大直径,子树外最长链与最大直径。

    设两棵树的直径分别是(A,B),那么连接两直径中点可得最小直径(max{A,B,lceilfrac A2 ceil+lceilfrac B2 ceil+1}),连接两直径端点可得最大直径(A+B+1)

    然后随便构造一下方案就好了。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 5e5+5;
    int n,to[N<<1],nxt[N<<1],head[N],cnt=1,d[N][3],w[N][2],g[N],o[N],h[N],ans1=1e9,edg1,ans2,edg2,ban[N<<1],pre[N],dep[N],S,tmp[N],len;
    void dfs1(int u,int f){
    	for (int e=head[u],v;e;e=nxt[e])
    		if ((v=to[e])!=f){
    			dfs1(v,u);
    			int t=d[v][0]+1;
    			if (t>d[u][0]) d[u][2]=d[u][1],d[u][1]=d[u][0],d[u][0]=t;
    			else if (t>d[u][1]) d[u][2]=d[u][1],d[u][1]=t;
    			else if (t>d[u][2]) d[u][2]=t;
    			t=g[v];g[u]=max(g[u],g[v]);
    			if (t>w[u][0]) w[u][1]=w[u][0],w[u][0]=t;
    			else if (t>w[u][1]) w[u][1]=t;
    		}
    	g[u]=max(g[u],d[u][0]+d[u][1]);
    }
    void dfs2(int u,int f){
    	for (int e=head[u],v;e;e=nxt[e])
    		if ((v=to[e])!=f){
    			o[v]=max(o[u],d[v][0]+1==d[u][0]?d[u][1]:d[u][0])+1;
    			h[v]=max(h[u],g[v]==w[u][0]?w[u][1]:w[u][0]);
    			int a,b;
    			if (d[v][0]+1==d[u][0]) a=d[u][1],b=d[u][2];
    			else a=d[u][0],b=d[v][0]+1==d[u][1]?d[u][2]:d[u][1];
    			h[v]=max(h[v],a+b);h[v]=max(h[v],a+o[u]);
    			int t=max(max(h[v],g[v]),(h[v]+1)/2+(g[v]+1)/2+1);
    			if (t<ans1) ans1=t,edg1=e;
    			t=h[v]+g[v]+1;
    			if (t>ans2) ans2=t,edg2=e;
    			dfs2(v,u);
    		}
    }
    void DFS(int u,int f){
    	pre[u]=f;dep[u]=dep[f]+1;S=dep[u]>dep[S]?u:S;
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=f&&!ban[e]) DFS(to[e],u);
    }
    int main(){
    	n=gi();
    	for (int i=1;i<n;++i){
    		int u=gi(),v=gi();
    		to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
    		to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    	}
    	dfs1(1,0);dfs2(1,0);
    	printf("%d %d %d ",ans1,to[edg1],to[edg1^1]);ban[edg1]=ban[edg1^1]=1;
    	DFS(S=to[edg1],0);DFS(S,len=0);
    	for (int i=S;i;i=pre[i]) tmp[++len]=i;printf("%d ",tmp[len+1>>1]);
    	DFS(S=to[edg1^1],0);DFS(S,len=0);
    	for (int i=S;i;i=pre[i]) tmp[++len]=i;printf("%d
    ",tmp[len+1>>1]);
    	printf("%d %d %d ",ans2,to[edg2],to[edg2^1]);ban[edg1]=ban[edg1^1]=0;ban[edg2]=ban[edg2^1]=1;
    	DFS(S=to[edg2],0);printf("%d ",S);
    	DFS(S=to[edg2^1],0);printf("%d
    ",S);
    	return 0;
    }
    

    [BZOJ4380][POI2015]Myjnie

    (f_{i,j,k})表示区间([i,j])最小费用为(k)的最大收益。为了方便转移记成后缀最大值的形式。

    暴力枚举区间内最小值的位置以及这个最小值转移,复杂度(O(n^3m))

    输出方案要多记录一些东西,比如(g_{i,j,k})记录后缀最大值的真实的(k)的位置,(h_{i,j,k})记录转移点(最小值的位置)。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 55;
    const int M = 4005;
    int n,m,a[M],b[M],c[M],o[M],tot[N][M],f[N][N][M],g[N][N][M],h[N][N][M],ans[N];
    void add(int l,int r){
    	for (int i=l;i<=r;++i)
    		for (int j=1;j<=m;++j)
    			tot[i][j]=0;
    	for (int i=1;i<=m;++i)
    		if (a[i]>=l&&b[i]<=r)
    			for (int j=a[i];j<=b[i];++j)
    				++tot[j][c[i]];
    	for (int i=l;i<=r;++i)
    		for (int j=m-1;j;--j)
    			tot[i][j]+=tot[i][j+1];
    }
    void dfs(int l,int r,int k){
    	if (l>r) return;
    	k=g[l][r][k];int x=h[l][r][k];
    	ans[x]=o[k];dfs(l,x-1,k);dfs(x+1,r,k);
    }
    int main(){
    	n=gi();m=gi();
    	for (int i=1;i<=m;++i) a[i]=gi(),b[i]=gi(),c[i]=o[i]=gi();
    	sort(o+1,o+m+1);
    	for (int i=1;i<=m;++i) c[i]=lower_bound(o+1,o+m+1,c[i])-o;
    	for (int i=n;i;--i)
    		for (int j=i;j<=n;++j){
    			add(i,j);
    			for (int k=m;k;--k){
    				int res=-1;
    				for (int x=i;x<=j;++x){
    					int tmp=f[i][x-1][k]+f[x+1][j][k]+tot[x][k]*o[k];
    					if (tmp>res) res=tmp,h[i][j][k]=x;
    				}
    				if (res>=f[i][j][k+1]) f[i][j][k]=res,g[i][j][k]=k;
    				else f[i][j][k]=f[i][j][k+1],g[i][j][k]=g[i][j][k+1];
    			}
    		}
    	printf("%d
    ",f[1][n][1]);dfs(1,n,1);
    	for (int i=1;i<=n;++i) printf("%d ",ans[i]);puts("");
    	return 0;
    }
    

    [BZOJ4381][POI2015]Odwiedziny

    POI居然会出(5w)的数据范围那就一定是根号算法了吧。

    按步伐大小分块:小于等于(sqrt n)的可以(O(nsqrt n))预处理每个点以(i)的步伐向上跳到根的权值和,大于(sqrt n)的暴力跳即可。处理起来或许有点小细节。

    这样复杂度是(O(nsqrt n))的。树剖跳(k)级祖先的复杂度看上去像是(O(log n))的,但对于一组询问重链的变换次数只有(O(log n)),所以单次的复杂度应该是(O(sqrt n+log n))而非两者乘起来。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 50005;
    const int M = 100;
    int n,m,a[N],to[N<<1],nxt[N<<1],head[N],cnt,fa[N],dep[N],sz[N],top[N],dfn[N],id[N],tim,s[N],sum[N][M],b[N],c[N];
    void dfs1(int u,int f){
    	fa[u]=f;dep[u]=dep[f]+1;sz[u]=1;
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=f) dfs1(to[e],u),sz[u]+=sz[to[e]];
    }
    void dfs2(int u,int f){
    	top[u]=f;id[dfn[u]=++tim]=u;int son=0;
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=fa[u]&&sz[to[e]]>sz[son]) son=to[e];
    	if (son) dfs2(son,f);else return;
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=fa[u]&&to[e]!=son) dfs2(to[e],to[e]);
    }
    void dfs(int u,int f){
    	s[dep[u]]=u;
    	for (int i=1;i<M;++i){
    		sum[u][i]=a[u];
    		if (dep[u]>i) sum[u][i]+=sum[s[dep[u]-i]][i];
    	}
    	for (int e=head[u];e;e=nxt[e])
    		if (to[e]!=f) dfs(to[e],u);
    }
    int father(int u,int k){
    	if (k>=dep[u]) return 0;
    	while (dep[u]-dep[top[u]]<k) k-=dep[u]-dep[top[u]]+1,u=fa[top[u]];
    	return id[dfn[u]-k];
    }
    int lca(int u,int v){
    	while (top[u]^top[v])
    		if (dep[top[u]]>dep[top[v]]) u=fa[top[u]];
    		else v=fa[top[v]];
    	return dep[u]<dep[v]?u:v;
    }
    int jump(int x,int d,int k){
    	if (k<M) return sum[x][k]-sum[father(x,(d/k+1)*k)][k];
    	int res=0;
    	while (d>=0) res+=a[x],d-=k,x=father(x,k);
    	return res;
    }
    int work(int x,int y,int k){
    	int z=lca(x,y),res=jump(x,dep[x]-dep[z],k);
    	if ((dep[x]+dep[y]-2*dep[z])%k) res+=a[y];
    	else y=father(y,(dep[x]+dep[y]-2*dep[z])%k);
    	res+=jump(y,dep[y]-dep[z],k);
    	if ((dep[x]-dep[z])%k==0) res-=a[z];
    	return res;
    }
    int main(){
    	n=gi();
    	for (int i=1;i<=n;++i) a[i]=gi();
    	for (int i=1;i<n;++i){
    		int u=gi(),v=gi();
    		to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
    		to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    	}
    	dfs1(1,0);dfs2(1,1);dfs(1,0);
    	for (int i=1;i<=n;++i) b[i]=gi();
    	for (int i=1;i<n;++i) c[i]=gi();
    	for (int i=1;i<n;++i) printf("%d
    ",work(b[i],b[i+1],c[i]));
    	return 0;
    }
    

    [BZOJ4382][POI2015]Podział naszyjnika

    对于两个相邻的同色块,我们可以在前一个上面打一个(+1)标记,在后一个上面打一个(-1)标记,然后就变成了选两个前缀他们的前缀和相等,相当于是这样个同色块之间不能割裂开来。

    但是有若干种颜色每种颜色有若干块,标记可能会冲突,怎么办呢?我们给每对相邻同色块随机打上不同的标记就行啦。

    这样第一问就是每种相同前缀数量(sz)(suminom{sz}{2}),第二问可以把这些前缀拿出来做一个单调队列优化转移的(dp)状物。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define ll long long
    const int N = 1e6+5;
    int n,k,lst[N],id[N],ans2=N;ll p=1,sum[N],ans1;
    bool cmp(int i,int j){return sum[i]==sum[j]?i<j:sum[i]<sum[j];}
    int cal(int i,int j){
    	int s=abs(n-(j-i<<1));
    	ans2=min(ans2,s);return s;
    }
    int main(){
    	n=gi();k=gi();
    	for (int i=1;i<=n;++i){
    		int a=gi();
    		if (lst[a]) sum[lst[a]]+=p,sum[i]-=p,p*=20020415;
    		lst[a]=i;
    	}
    	for (int i=1;i<=n;++i) sum[i]+=sum[i-1],id[i]=i;
    	sort(id+1,id+n+1,cmp);
    	for (int i=1,j=1;i<=n;i=j=j+1){
    		while (j<n&&sum[id[j+1]]==sum[id[i]]) ++j;
    		ans1+=1ll*(j-i)*(j-i+1)>>1;
    		for (int k=i,l=i;k<=j;++k)
    			while (l<k&&cal(id[l+1],id[k])<cal(id[l],id[k])) ++l;
    	}
    	printf("%lld %d
    ",ans1,ans2);
    	return 0;
    }
    

    [BZOJ4383][POI2015]Pustynia

    暴力建图就是个差分约束状物。拿线段树优化连边可以做到边数(O(sum klog n))

    注意判一下无解,包括可能某个数超过了(10^9)也算是无解。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 4e5+5;
    int n,s,m,tot,pos[N],root,ls[N],rs[N],tmp[N],to[N*20],nxt[N*20],ww[N*20],head[N],cnt,in[N],f[N],t[N];
    queue<int>Q;
    void link(int u,int v,int w){
    	to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=w;head[u]=cnt;++in[v];
    }
    void build(int &x,int l,int r){
    	x=++tot;if (l==r) {pos[l]=x;return;}
    	int mid=l+r>>1;
    	build(ls[x],l,mid);build(rs[x],mid+1,r);
    	link(ls[x],x,0);link(rs[x],x,0);
    }
    void modify(int x,int l,int r,int ql,int qr){
    	if (l>=ql&&r<=qr) {link(x,tot,0);return;}
    	int mid=l+r>>1;
    	if (ql<=mid) modify(ls[x],l,mid,ql,qr);
    	if (qr>mid) modify(rs[x],mid+1,r,ql,qr);
    }
    int main(){
    	n=gi();s=gi();m=gi();
    	build(root,1,n);memset(t,63,sizeof(t));
    	while (s--){
    		int p=gi();f[pos[p]]=t[pos[p]]=gi();
    	}
    	while (m--){
    		int l=gi(),r=gi(),k=gi();tmp[0]=l-1,tmp[k+1]=r+1;++tot;
    		for (int i=1;i<=k;++i) tmp[i]=gi(),link(tot,pos[tmp[i]],1);
    		for (int i=0;i<=k;++i) if (tmp[i+1]-tmp[i]>1) modify(root,1,n,tmp[i]+1,tmp[i+1]-1);
    	}
    	for (int i=1;i<=tot;++i) if (!in[i]) f[i]=max(f[i],1),Q.push(i);
    	while (!Q.empty()){
    		int u=Q.front();Q.pop();
    		for (int e=head[u];e;e=nxt[e]){
    			f[to[e]]=max(f[to[e]],f[u]+ww[e]);
    			if (f[to[e]]>t[to[e]]) return puts("NIE"),0;
    			if (!--in[to[e]]) Q.push(to[e]);
    		}
    	}
    	for (int i=1;i<=n;++i) if (!f[pos[i]]||f[pos[i]]>1000000000) return puts("NIE"),0;
    	puts("TAK");
    	for (int i=1;i<=n;++i) printf("%d ",f[pos[i]]);
    	puts("");return 0;
    }
    

    [BZOJ4384][POI2015]Trzy wieże

    首先只有一种字符的可以预先(O(n))判掉。然后就是三种字符的出现次数均不相同。

    对每个位置分别记下三种字符的前缀数量(cnt_{i,0/1/2})

    问题要求的是:

    [cnt_{i,0}-cnt_{j,0} eq cnt_{i,1}-cnt_{j,1}\cnt_{i,0}-cnt_{j,0} eq cnt_{i,2}-cnt_{j,2}\cnt_{i,1}-cnt_{j,1} eq cnt_{i,2}-cnt_{j,2} ]

    也就是

    [cnt_{i,0}-cnt_{i,1} eq cnt_{j,0}-cnt_{j,1}\cnt_{i,0}-cnt_{i,2} eq cnt_{j,0}-cnt_{j,2}\cnt_{i,1}-cnt_{i,2} eq cnt_{j,1}-cnt_{j,2} ]

    (a_i=cnt_{i,0}-cnt_{i,1},b_i=cnt_{i,0}-cnt_{i,2},c_i=cnt_{i,1}-cnt_{i,2}),相当于是要求(max{i-j|a_i eq a_j,b_i eq b_j,c_i eq c_j})

    (a_i)排序,以(b_i)为下标插入树状数组,树状数组每个点上维护下标的最大最小值。因为有(c_i)的限制,所以要维护(c_i)值不同的最大次大值与最小次小值。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    const int N = 1e6+5;
    int n,cnt[3],a[N],b[N],c[N],o[N],len,id[N],ans;char s[N];
    bool cmp(int i,int j){return a[i]<a[j];}
    struct node{
    	int mx1,mx2,mn1,mn2;
    	node(){mx1=mx2=0;mn1=mn2=N-1;}
    	void upt(int x){
    		if (x>mx1){
    			if (c[x]!=c[mx1]) mx2=mx1;
    			mx1=x;
    		}
    		else if (x>mx2&&c[x]!=c[mx1]) mx2=x;
    		if (x<mn1){
    			if (c[x]!=c[mn1]) mn2=mn1;
    			mn1=x;
    		}
    		else if (x<mn2&&c[x]!=c[mn1]) mn2=x;
    	}
    };
    struct Binary_Index_Tree{
    	node t[N];
    	void mdf(int k,int v){while(k<=len)t[k].upt(v),k+=k&-k;}
    	int qry_mx(int k,int ban){
    		int s=0;
    		while (k) s=max(s,c[t[k].mx1]==ban?t[k].mx2:t[k].mx1),k^=k&-k;
    		return s;
    	}
    	int qry_mn(int k,int ban){
    		int s=n+1;
    		while (k) s=min(s,c[t[k].mn1]==ban?t[k].mn2:t[k].mn1),k^=k&-k;
    		return s;
    	}
    }T[2];
    int main(){
    	n=gi()+1;scanf("%s",s+2);
    	for (int i=1;i<=n;++i){
    		if (i>1) ++cnt[s[i]=='S'?2:s[i]-'B'];
    		a[i]=cnt[1]-cnt[0];b[i]=cnt[2]-cnt[0];c[i]=cnt[2]-cnt[1];
    	}
    	for (int i=1;i<=n;++i) o[i]=b[i],id[i]=i;
    	sort(o+1,o+n+1);len=unique(o+1,o+n+1)-o-1;
    	for (int i=1;i<=n;++i) b[i]=lower_bound(o+1,o+len+1,b[i])-o;
    	sort(id+1,id+n+1,cmp);
    	for (int i=1,j=1;i<=n;i=j=j+1){
    		while (j<n&&a[id[j+1]]==a[id[i]]) ++j;
    		for (int k=i;k<=j;++k){
    			int x=id[k];
    			ans=max(ans,T[0].qry_mx(b[x]-1,c[x])-x);
    			ans=max(ans,x-T[0].qry_mn(b[x]-1,c[x]));
    			ans=max(ans,T[1].qry_mx(len-b[x],c[x])-x);
    			ans=max(ans,x-T[1].qry_mn(len-b[x],c[x]));
    		}
    		for (int k=i;k<=j;++k){
    			int x=id[k];
    			T[0].mdf(b[x],x);T[1].mdf(len-b[x]+1,x);
    		}
    	}
    	for (int i=2,j;i<=n;++i){
    		if (s[i]!=s[i-1]) j=0;
    		++j;ans=max(ans,j);
    	}
    	printf("%d
    ",ans);return 0;
    }
    

    [BZOJ4385][POI2015]Wilcze doły

    一定会选长度为(d)的区间。(mbox{Two-points})+单调队列维护即可。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define ll long long
    const int N = 2e6+5;
    int n,d,q[N],hd=1,tl,ans;ll p,sum[N],val[N];
    int main(){
    	n=gi();scanf("%lld",&p);d=gi();
    	for (int i=1;i<=n;++i) sum[i]=sum[i-1]+gi(),val[i]=sum[i]-sum[max(0,i-d)];
    	for (int i=1,j=1;i<=n;++i){
    		while (hd<=tl&&val[q[tl]]<=val[i]) --tl;
    		q[++tl]=i;
    		while (sum[i]-sum[j-1]-val[q[hd]]>p){
    			++j;while (q[hd]-d+1<j) ++hd;
    		}
    		ans=max(ans,i-j+1);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    [BZOJ4386][POI2015]Wycieczki

    裸的倍增矩乘。新建一个(0)号点表示路径的终点即可。

    (mbox{long long})这点很烦,代码中判断如果乘出来的数超过了(K)就直接赋成(-1)

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int gi(){
    	int x=0,w=1;char ch=getchar();
    	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if (ch=='-') w=0,ch=getchar();
    	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    	return w?x:-x;
    }
    #define ll long long
    const int N = 125;
    int n,m,sz;ll K,ans;
    struct matrix{
    	ll a[N][N];
    	matrix(){memset(a,0,sizeof(a));}
    	ll *operator [](int x){return a[x];}
    	matrix operator * (matrix b){
    		matrix c;
    		for (int i=0;i<=sz;++i)
    			for (int j=0;j<=sz;++j)
    				for (int k=0;k<=sz;++k)
    					if (a[i][k]&&b[k][j]){
    						if (a[i][k]<0||b[k][j]<0) {c[i][j]=-1;break;}
    						if (a[i][k]>K/b[k][j]) {c[i][j]=-1;break;}
    						c[i][j]+=a[i][k]*b[k][j];
    						if (c[i][j]<0) {c[i][j]=-1;break;}
    					}
    		return c;
    	}
    	bool check(){
    		ll res=0;
    		for (int i=1;i<=n;++i){
    			if (a[i][0]<0) return false;
    			res+=a[i][0];
    			if (res>=K||res<0) return false;
    		}
    		return true;
    	}
    }T[63],Now,Tmp;
    int main(){
    	n=gi();m=gi();sz=3*n;scanf("%lld",&K);K+=n;T[0][0][0]=1;
    	for (int i=1;i<=n;++i) Now[i][i]=T[0][i][0]=T[0][i][i+n]=T[0][i+n][i+n+n]=1;
    	for (int i=1,u,v,w;i<=m;++i) u=gi(),v=gi(),w=gi()-1,++T[0][u+w*n][v];
    	for (int i=1;i<63;++i) T[i]=T[i-1]*T[i-1];
    	for (int i=62;~i;--i){
    		Tmp=Now*T[i];
    		if (Tmp.check()) ans|=1ll<<i,Now=Tmp;
    	}
    	if ((Now*T[0]).check()) puts("-1");
    	else printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    【.Net Micro Framework PortingKit 07】NVIC中断处理
    【.Net Micro Framework PortingKit 02】STM3210E平台构建
    【.Net Micro Framework PortingKit 06】设置芯片时钟
    【.Net Micro Framework PortingKit 03】调试初步:点亮LED灯
    【.Net Micro Framework PortingKit 01】移植初步:环境搭建
    开源System.Windows.Forms库,让.Net Micro Framework界面开发和上位机一样简单
    RVDS和MDK嵌入式开发工具调试脚本编写
    JQuery移除事件 简单
    Visual C++ 2008入门经典 第十六章 创建文档和改进视图 简单
    Visual C++ 2008入门经典 第十五章 在窗口中绘图 简单
  • 原文地址:https://www.cnblogs.com/zhoushuyu/p/9775146.html
Copyright © 2011-2022 走看看