zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Eclipse打jar包的方法
    Eclipse中SVN插件的安装
    无法远程访问 MySql Server
    TCP(Socket基础编程)
    安装淘宝镜像cnpm时出现问题及解决方案
    搭建vue脚手架---vue-cli
    padding和margin设置成百分比
    响应式布局
    响应式网页:用em,rem设置网页字体大小自适应
    url两次编码
  • 原文地址:https://www.cnblogs.com/zhoushuyu/p/9745613.html
Copyright © 2011-2022 走看看