POI2013题解

POI2013题解

只做了BZ上有的(13)道题。

就这样还扔了两道神仙构造和一道计算几何题。所以只剩下十道题了。

[BZOJ3414][Poi2013]Inspector

肯定是先二分答案,然后每个人的出现区间至少要包含于他自己记录的所有时间点。如果某个人没有记录过那他的出现区间任意。

从左往右扫描,维护以下几个东西:

(t):目前还有多少人的区间不确定。

(s):当前要求选多少人。(这个是由记录者决定的)

(cl):有多少人的区间可以向左扩展。

(cr):有多少人的区间可以向右扩展。

每扫到一个位置,如果(s>)记录的人数则无解,否则(s+cl+cr)就是当前可存在于该位置上的人数,若人多了就优先减(cr)再减(cl),若人少了就从(t)中拿人补到(cl)中。

#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 = 1e5+5;
int n,m,tim[N],peo[N],rec[N],L[N],R[N],num[N],pl[N],pr[N];
bool check(int mid){
	for (int i=1;i<=n;++i) L[i]=N,R[i]=0;
	for (int i=1;i<=m;++i) num[i]=pl[i]=pr[i]=0;
	for (int i=1;i<=mid;++i){
		int t=tim[i],p=peo[i],r=rec[i];
		if (num[t]&&num[t]!=r) return false;
		num[t]=r;L[p]=min(L[p],t);R[p]=max(R[p],t);
	}
	for (int i=1;i<=n;++i) if (R[i]) ++pl[L[i]],++pr[R[i]];
	for (int i=1,t=n,s=0,cl=0,cr=0;i<=m;++i)
		if (num[i]){
			s+=pl[i];
			while (pl[i]--) cl?--cl:--t;
			if (s>num[i]) return false;
			while (s+cl+cr<num[i]) ++cl,--t;
			while (s+cl+cr>num[i]) cr?--cr:--cl;
			s-=pr[i];cr+=pr[i];
			if (t<0) return false;
		}
	return true;
}
int main(){
	int Case=gi();while (Case--){
		n=gi();m=gi();
		for (int i=1;i<=m;++i) tim[i]=gi(),peo[i]=gi(),rec[i]=gi()+1;
		int l=0,r=m,res=0;
		while (l<=r){
			int mid=l+r>>1;
			if (check(mid)) res=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d
",res);
	}
	return 0;
}

[BZOJ3415][Poi2013]Price List

可知有且仅有三种策略:

1、只走(a)边,可以当做(b)边不存在。

2、把两条(a)边并做一条(b)边走。

3、一条(a)边都不会走。

前两种直接在原图中(bfs)即可出解。

考虑第三种,如果在(bfs)的过程中对于每个点(x)枚举所有与他距离为(2)的点,复杂度会退化成(O(m^2))

假设我们有两个边集(G_0,G_1),初始时都为原图。从队列中取出一个点(u),先枚举其在(G_0)中的所有出边指向的点(v)并标记,再顺次枚举每个(v)(G_1)中的出边指向的点(w),如果(w)未被标记((u,v,w)三点不构成三元环)则可用(u)更新(w),更新后把(G_1)(v o w)的这条边删除。

在不考虑三元环的前提下,(G_0)每条边被访问一起,(G_1)每条边也被访问一次,所以复杂度线性。但是构成三元环会导致边(v o w)不能删。而三元环的数量是(O(msqrt m))的,每个三元环会给复杂度带来一个常数(会被访问(3)次),所以复杂度为(O(msqrt m))

#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 = 2e5+5;
struct Graph{
	int to[N],nxt[N],hd[N],cnt;
	void link(int u,int v){
		to[++cnt]=v;nxt[cnt]=hd[u];hd[u]=cnt;
		to[++cnt]=u;nxt[cnt]=hd[v];hd[v]=cnt;
	}
}G,F;
int n,m,S,A,B,dep[N],vis[N],ans[N];queue<int>Q;
int main(){
	n=gi();m=gi();S=gi();A=gi();B=gi();
	for (int i=1;i<=m;++i){
		int u=gi(),v=gi();
		G.link(u,v);F.link(u,v);
	}
	dep[S]=1;Q.push(S);
	while (!Q.empty()){
		int u=Q.front();Q.pop();
		for (int e=G.hd[u];e;e=G.nxt[e]){
			int v=G.to[e];
			if (!dep[v]) dep[v]=dep[u]+1,Q.push(v);
		}
	}
	for (int i=1;i<=n;++i) --dep[i],ans[i]=min(dep[i]*A,(dep[i]>>1)*B+(dep[i]&1)*A);
	memset(dep,0,sizeof(dep));dep[S]=1;Q.push(S);
	while (!Q.empty()){
		int u=Q.front();Q.pop();
		for (int e=G.hd[u];e;e=G.nxt[e]) vis[G.to[e]]=u;
		for (int e=G.hd[u];e;e=G.nxt[e]){
			int v=G.to[e];
			for (int i=F.hd[v],las=0;i;i=F.nxt[i]){
				int w=F.to[i];
				if (vis[w]==u) {las=i;continue;}
				if (!dep[w]) dep[w]=dep[u]+1,Q.push(w);
				if (!las) F.hd[v]=F.nxt[F.hd[v]];
				else F.nxt[las]=F.nxt[i];
			}
		}
	}
	for (int i=1;i<=n;++i) if (dep[i]) --dep[i],ans[i]=min(ans[i],dep[i]*B);
	for (int i=1;i<=n;++i) printf("%d
",ans[i]);
	return 0;
}

[BZOJ3416][Poi2013]Take-out

从前往后依次压进栈中,如果栈内的前(k+1)个元素中恰好有一个白的就把这(k+1)个作为一组方案。倒序输出即可。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e6+5;
int n,k,q[N],sum[N],top,ans[N],cnt;char s[N];
int main(){
	scanf("%d%d%s",&n,&k,s+1);++k;
	for (int i=1;i<=n;++i){
		q[++top]=i;sum[top]=sum[top-1]+(s[i]=='c');
		if (top>=k&&sum[top]-sum[top-k]==1)
			for (int tt=top-k;top>tt;--top)
				ans[++cnt]=q[top];
	}
	for (int i=n;i;--i) printf("%d%c",ans[i],i%k==1?'
':' ');
	return 0;
}

[BZOJ3417][Poi2013]Tales of seafaring

(O(n^2))预处理出(i)(j)走奇数/偶数步的最短路。需要特判孤立点走大于零的偶数步到达自身是不合法的。

有人说预处理存不下?最短路长度最多(2n)你开个(mbox{short})存不就好啦?

#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 = 5005;
short n,m,to[N<<1],nxt[N<<1],head[N],cnt,q[N<<1],hd,tl,dep[N][N<<1];
int k;
int main(){
	n=gi();m=gi();k=gi();
	for (short i=1;i<=m;++i){
		short u=gi(),v=gi();
		to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
		to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
	}
	memset(dep,-1,sizeof(dep));
	for (short i=1;i<=n;++i){
		dep[i][i<<1]=0;q[hd=tl=1]=i<<1;
		while (hd<=tl){
			short x=q[hd++],u=x>>1,c=(x&1)^1;
			for (short e=head[u];e;e=nxt[e])
				if (!~dep[i][to[e]<<1|c])
					dep[i][to[e]<<1|c]=dep[i][x]+1,q[++tl]=to[e]<<1|c;
		}
	}
	while (k--){
		short u=gi(),v=gi();int d=gi();
		if (u==v&&!head[u]&&d>0) puts("NIE");
		else if (dep[u][v<<1|(d&1)]!=-1&&(int)dep[u][v<<1|(d&1)]<=d) puts("TAK");
		else puts("NIE");
	}
	return 0;
}

[BZOJ3419][Poi2013]Taxis

首先可以发现的是,从大到小叫车是最优的,而且如果已行走距离超过了(d)就只需要一辆车就可以到终点了。

先从大到小叫车,这样如果有解的话一定是最优解,但是可能会无解,就比如说把所有(ge m-d)的车全耗在了前半段导致后半段过不去。还是贪心,选出(ge m-d)的最小的留给后半段,剩下的还是贪心选。这样就能得到答案了。

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll gi(){
	ll 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,p,ans;ll m,d,a[N],now;
bool cmp(ll i,ll j){return i>j;}
void cal(ll x){if(now<d)now+=max(0ll,x-d+now);else now=max(now,d+x);}
int main(){
	m=gi();d=gi();n=gi();
	for (int i=1;i<=n;++i) a[i]=gi();
	sort(a+1,a+n+1,cmp);
	for (int i=1;i<=n;++i){
		cal(a[i]);if (now>=m) {ans=i;break;}
		if (a[i]>=m-d) p=i;
	}
	if (!ans){
		now=0;ll pos=d-(a[p]-m+d>>1);
		for (int i=1;i<=n;++i)
			if (i^p){
				cal(a[i]);if (now>=pos) {ans=i;break;}
			}
	}
	printf("%d
",ans);return 0;
}

[BZOJ3420][Poi2013]Triumphal arch

先二分答案,设(f_i)表示在(i)节点至少需要预先在(i)子树内标记多少个点,转移(f_i=max{0,sum_j (f_j+1)-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 = 6e5+5;
int n,to[N],nxt[N],head[N],cnt;
int dfs(int u,int f,int k){
	int s=0;
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=f) s+=dfs(to[e],u,k)+1;
	return max(0,s-k);
}
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;
	}
	int l=0,r=n-1,res=0;
	while (l<=r){
		int mid=l+r>>1;
		if (!dfs(1,0,mid)) res=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d
",res);return 0;
}

[BZOJ3421][Poi2013]Walk

有一个结论:在一个(n)维超立方体上有(2^n)个点,如果把这(2^n)个点分成(2)个点集,那么这样个点集之间的边数至少为两个点集的点数较小值。根据这个假设是已知的结论就可以证明一个定理:删除(k)个点后,图中存在至多一个大小大于(nk)的连通块,太懒了就不在这里证了。

所以直接(bfs),从(S)出发只扩展至多(nk)个状态,如果两边搜出了超过(nk)个状态还没有相遇就说明两点都在那个大于(nk)的连通块里面。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
const int N = 5e6+500;
const int mod = 5170427;
int n,k,nk,hd,tl,nxt[N],head[mod],cnt;ll a[N>>2],q[N],to[N];
char s[100];
ll read(){
	scanf("%s",s);ll res=0;
	for (int i=0;i<n;++i) res=res<<1|s[i]-'0';
	return res;
}
void add1(ll x){
	to[++cnt]=x;nxt[cnt]=head[x%mod];head[x%mod]=cnt;
}
void add2(ll x){
	for (int e=head[x%mod];e;e=nxt[e])
		if (to[e]==x) return;
	q[++tl]=to[++cnt]=x;nxt[cnt]=head[x%mod];head[x%mod]=cnt;
}
void work(ll S,ll T){
	memset(head,0,sizeof(head));cnt=0;
	hd=1;tl=0;add2(S);
	for (int i=1;i<=k;++i) add1(a[i]);
	while (hd<=tl&&tl<=nk){
		ll u=q[hd++];if (u==T) puts("TAK"),exit(0);
		for (int i=0;i<n;++i) add2(u^(1ll<<i));
	}
	if (tl<=nk) puts("NIE"),exit(0);
}
int main(){
	scanf("%d%d",&n,&k);nk=n*k;
	ll S=read(),T=read();
	for (int i=1;i<=k;++i) a[i]=read();
	work(S,T);work(T,S);
	puts("TAK");return 0;
}

[BZOJ3425][Poi2013]Polarization

最小答案一定是(n-1),只要按边深度的奇偶性取不同的方向就可以取到了。

最大答案存在一个结论,就是以重心为根,剩下每棵子树要么全部指向根,要么全部背离根。假设指向根的子树大小之和是(x),那么背离根的子树大小之和就是(n-x-1),所有穿过根的贡献就是(x(n-x-1))

所以现在要做的就是拿重心的所有儿子出来做一个(01)背包看可以组成哪些(x)。直接背包压位的话复杂度是(O(frac{n^2}{32})),然后把(ge sqrt n)的暴力做,(< sqrt n)的放在一起二进制分组做,复杂度就是(O(frac{nsqrt n}{32}))

#include<cstdio>
#include<algorithm>
#include<bitset>
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 = 250005;
int n,to[N<<1],nxt[N<<1],head[N],cnt,sz[N],w[N],rt,s[505];
ll f[N];bitset<N>g;
void dfs(int u,int fa){
	sz[u]=1;w[u]=f[u]=0;
	for (int e=head[u],v;e;e=nxt[e])
		if ((v=to[e])!=fa){
			dfs(v,u);sz[u]+=sz[v];
			f[u]+=f[v]+sz[v];w[u]=max(w[u],sz[v]);
		}
	w[u]=max(w[u],n-sz[u]);if (w[u]<w[rt]) rt=u;
}
int main(){
	n=gi();if (n==1) return puts("0 0"),0;
	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;
	}
	w[0]=n;dfs(1,0);dfs(rt,0);g[0]=1;
	for (int e=head[rt];e;e=nxt[e])
		if (sz[to[e]]>=500) g|=g<<sz[to[e]];
		else ++s[sz[to[e]]];
	for (int i=1;i<500;++i)
		for (int j=s[i],k=1;j;j-=k,k=min(j,k<<1))
			g|=g<<i*k;
	for (int i=n>>1;i;--i)
		if (g[i]) return printf("%d %lld
",n-1,f[rt]+1ll*i*(n-i-1)),0;
}

[BZOJ3426][Poi2013]Tower Defense Game

直接每次选一个没覆盖的点就行了,因为这样做一定可以包含至少一个最优方案中的选择点。

至于维护这个覆盖,对每个点记一下最近的选择点到他的距离(代码中有点不一样,没关系啦),覆盖时如果不能更新这个距离就不走了(因为之前肯定已经走过了)。考虑到每个点的这个距离只会被更新常数次,所以复杂度是对的。

#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 = 2e6+5;
int n,m,to[N],nxt[N],head[N],cnt,vis[N],q[N],ans;
void dfs(int u,int c){
	vis[u]=c;
	for (int e=head[u];e;e=nxt[e])
		if (vis[to[e]]<c-1) dfs(to[e],c-1);
}
int main(){
	n=gi();m=gi();gi();
	for (int i=1;i<=m;++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;
	}
	for (int i=1;i<=n;++i) if (!vis[i]) q[++ans]=i,dfs(i,3);
	printf("%d
",ans);
	for (int i=1;i<=ans;++i) printf("%d ",q[i]);
	puts("");return 0;
}

[BZOJ3427][Poi2013]Bytecomputer

猜个结论最终序列一定是一段(-1)一段(0)一段(1)

这样以来就直接(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,f[N][3];
int main(){
	memset(f,63,sizeof(f));
	n=gi();f[1][gi()+1]=0;
	for (int i=2;i<=n;++i){
		int x=gi();
		if (x==1){
			f[i][2]=min(f[i-1][2],min(f[i-1][0],f[i-1][1]));
			f[i][1]=f[i-1][0]+1;f[i][0]=f[i-1][0]+2;
		}else if (x==0){
			f[i][1]=min(f[i-1][1],f[i-1][0]);
			f[i][2]=f[i-1][2]+1;f[i][0]=f[i-1][0]+1;
		}else f[i][0]=f[i-1][0],f[i][2]=f[i-1][2]+2;
	}
	int ans=min(f[n][0],min(f[n][1],f[n][2]));
	if (ans==f[0][0]) puts("BRAK");
	else printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/zhoushuyu/p/9745613.html