ZOJ Monthly, December 2013

部分题解(3738,3740,3741,3742,3745),其他题目以后会做了再更新吧。。。



ZOJ 3738 Buy the Pets


状压dp,如果压20位(10位猫,10位狗),显然复杂度不够。

观察一下发现人和猫有冲突,狗和猫有冲突,人和狗无冲突。那么可以压10位,人选猫,狗选猫,再乘起来就是答案。复杂度O(10*10*2^10)。

转移:

dp[i][1<<j^st]+=dp[i-1][st] (i与j不冲突)

dp[i][st]+=dp[i-1][st]


code:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <set>
#include <map>
using namespace std;

#define N  50100
#define ll long long
#define ALL(x)     x.begin(),x.end()
#define CLR(x,a)   memset(x,a,sizeof(x))
typedef pair<int,int> PI;
const int    INF=0x3fffffff;
const int    MOD=1000000007;
const double EPS=1e-9;
/*----------------code-----------------*/
bool r[2][16][16];
ll dp[2][16][1<<10];
int p,c,d,m;

int who(int &x){
	int type=0;
	x--;
	if(x-p>=0) x-=p, type++; else return type;
	if(x-c>=0) x-=c, type++; 
	return type;
}

void DP(int n,bool (*g)[16],ll (*f)[1<<10]){
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<c);j++){
			f[i][j]+=f[i-1][j];
			for(int k=0;k<c;k++) if(!(1<<k&j)){
				if(g[i-1][k]) continue;
				f[i][1<<k^j]+=f[i-1][j];
			}
		}
	}
}

int main(){
	while(~scanf("%d%d%d",&p,&c,&d)){
		scanf("%d",&m);
		CLR(r,0);
		CLR(dp,0);
		while(m--){
			int x,y;
			scanf("%d%d",&x,&y);
			int u=who(x),v=who(y),op=(u+v)%3;
			if(v!=1) swap(x,y);
			r[op][x][y]=true;
		}
		DP(p,r[1],dp[1]);  //people choose cat
		DP(d,r[0],dp[0]);  //dog choose cat
		ll ans=0;
		for(int i=0;i<(1<<c);i++){
			if(__builtin_popcount(i)!=p) continue;
			ans+=dp[1][p][i]*dp[0][d][i];
		}
		printf("%lld\n",ans);
	}
	return 0;
}



ZOJ 3740 Water Level


对于每个A[i],有一个区间[ 1-A[i], n-A[i] ]。如果C落在这个区间里,那么A[i]还是符合要求的。问题就变成找一个C,被覆盖的次数最多。

不改变和改变一次都好处理。我们分析一下改变两次怎么求。



绿色线是第一次改变,红色是第二次(i<j)。记cnt[c,i]表示从i到n,c被覆盖的次数。sum[i]表示1到i符合条件的个数。

两次改变后,答案ans=cnt[c1,i]-cnt[c1,j]+cnt[c3,j]+sum[i-1]。

变换下顺序cnt[c1,i]+sum[i-1]+(cnt[c3,j]-cnt[c1,j])

如果i和c1确定了,那么cnt[c1,i]+sum[i-1]是固定部分,我们只要使得Max=(cnt[c3,j]-cnt[c1,j])的值最大化就好,至于j具体在哪里可以不管。

显然Max=max{max{cnt[c,j]} - cnt[c1,j]}(j>i)

从后往前推自然能得到每一个c1对应的Max,cnt[][]也不需要二维了。

维护更新每个c1的Max,用一个数组d[]记录。

复杂度O(n^2)


code:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <set>
#include <map>
using namespace std;

#define N  3010
#define ll long long
#define ALL(x)     x.begin(),x.end()
#define CLR(x,a)   memset(x,a,sizeof(x))
typedef pair<int,int> PI;
const int    INF=0x3fffffff;
const int    MOD=1000000007;
const double EPS=1e-9;
/*----------------code-----------------*/

int cnt[N*3],d[N*3],a[N],sum[N];

int main(){
	int n,Max,ans;
	while(~scanf("%d",&n)){
		ans=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if(1<=a[i] && a[i]<=n) ans++;
			sum[i]=ans;
		}
		CLR(cnt,0);
		CLR(d,0);
		Max=0;
		for(int i=n;i>=1;i--){
			for(int c=1-a[i];c<=n-a[i];c++) Max=max(Max,++cnt[c+n]);
			for(int c=-n+1;c<=2*n;c++){
				ans=max(ans,cnt[c+n]+sum[i-1]); 		//change once
			   	ans=max(ans,cnt[c+n]+d[c+n]+sum[i-1]);  //change twices
			}
			for(int c=-n+1;c<=2*n;c++) d[c+n]=max(d[c+n],Max-cnt[c+n]);
		}
		printf("%d\n",ans);
	}
	return 0;
}


ZOJ 3741 Eternal Reality


dp[i]表示前i-1轮已经完成,第i轮可以Level Upper,此时的最大值。用Max[i]表示在[i,i+x+y-1]这段区间内能获得的最大值(使用Level Upper或不使用)。sum[i]表示前i轮能获得的值。

转移:

dp[i]=max{ dp[j]+Max[j]+(sum[i]-sum[j+x+y-1])  | j+x+y-1<i }

所求的答案在dp[n+1],但是n+1这个位置比较特殊,它不需要要求第n+1轮可以Level Upper,因为他根本就不存在。

所以n+1的时候,转移的范围是j<=n

还有一个坑,就是LV5不能增加到LV6


code:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <set>
#include <map>
using namespace std;
    
#define N  100100
#define ll long long
#define ALL(x)     x.begin(),x.end()
#define CLR(x,a)   memset(x,a,sizeof(x))
typedef pair<int,int> PI;
const int    INF=0x3fffffff;
const int    MOD=1000000007;
const double EPS=1e-9;

int a[N],dp[N],Max[N],sum[N];

int main(){
	int L,n,x,y;
	while(~scanf("%d%d%d%d",&L,&n,&x,&y)){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		CLR(dp,0);
		for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(a[i]<=L);
		if(L<5) L++;
		for(int i=1;i<=n;i++){
			int tot=0;
			for(int j=i;j<=min(n,i+x-1);j++) if(a[j]<=L) tot++;
			for(int j=i+x;j<=min(n,i+x+y-1);j++) if(a[j]<=0) tot++;
			Max[i]=max(tot,sum[min(n,i+x+y-1)]-sum[i-1]);
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j+x+y-1<i;j++)
				dp[i]=max(dp[i],dp[j]+Max[j]+sum[i-1]-sum[j+x+y-1]);
			dp[i]=max(dp[i],sum[i-1]);
		}
		int ans=0;
		for(int i=1;i<=n;i++) 
			ans=max(ans,dp[i]+Max[i]+sum[n]-sum[min(n,i+x+y-1)]);
		printf("%d\n",ans);
	}
	return 0;
}


ZOJ 3742 Bellywhite's Algorithm Homework


按点连的边数分为>sqrt(m)的和小于等于sqrt(m)的。

对于小的点,每次更新直接暴力每条边即可。

对于大的点,记录整数和与负数和,每次更新交换一下,同时要更新与它有交集的大的点。也就是说一开始还要预处理出2个大的点之间交集的整数和与负数和。

查询的话,记录一个全局的整数和与负数和,每次都能O(1)回答。

具体更新细节可以看代码。复杂度O(Qsqrt(m))。代码还有可以优化的地方,虽然有重边,但是两点之间可以压缩成两条边,正边,负边。


code:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <set>
#include <map>
using namespace std;

#define N  50100
#define ll long long
#define ALL(x)     x.begin(),x.end()
#define CLR(x,a)   memset(x,a,sizeof(x))
typedef pair<int,int> PI;
const int    INF=0x3fffffff;
const int    MOD=1000000007;
const double EPS=1e-9;

int n,m,limit,num[N];
ll POS,NEG,pos[N],neg[N];
map<PI,pair<ll,ll> > crs;
vector<int> e[N],big;
struct Edge{
	int u,v;
	ll val;
}edge[N];

void change(int x){
	if(e[x].size()>limit){
		for(int i=0;i<big.size();i++){
			int u=x,v=big[i];
			if(u>v) swap(u,v);
			map<PI,pair<ll,ll> >::iterator it;
			if((it=crs.find(make_pair(u,v)))!=crs.end()){
				v=big[i];
				pair<ll,ll> &p=it->second;
				pos[v]-=p.first, neg[v]-=p.second;
				swap(p.first,p.second);
				p.first=-p.first;
				p.second=-p.second;
				pos[v]+=p.first, neg[v]+=p.second;
			}
		}
		POS-=pos[x], NEG-=neg[x];
		swap(pos[x],neg[x]);
		pos[x]=-pos[x];
		neg[x]=-neg[x];
		POS+=pos[x], NEG+=neg[x];
	}else{
		for(int i=0;i<e[x].size();i++){
			int eid=e[x][i];
			int u=edge[eid].u, v=edge[eid].v, val=edge[eid].val;
			if(u!=x) swap(u,v);
			if(e[v].size()>limit){
				if(num[u]^num[v]) val=-val;
			   	pos[v]-=val, neg[v]-=val;
			}else{
			   	edge[eid].val=-val;
			}
			POS-=val, NEG-=val;
		}
	}
	num[x]^=1;
}

void pretreat(){
	for(int i=1;i<=n;i++) if(e[i].size()>limit){
		big.push_back(i);
	}
	for(int i=0;i<m;i++){
		int u=edge[i].u, v=edge[i].v;
		if(edge[i].val>0){
			pos[u]+=edge[i].val;
			pos[v]+=edge[i].val;
		}else{
			neg[u]+=edge[i].val;
			neg[v]+=edge[i].val;
		}
		if(e[u].size()>limit && e[v].size()>limit){
			if(u>v) swap(u,v);
			if(edge[i].val>0) 
				crs[make_pair(u,v)].first+=edge[i].val;
			else 
				crs[make_pair(u,v)].second+=edge[i].val;
		}	
	}
}

void clear(){
	big.clear();
	crs.clear();
	CLR(num,0);
	CLR(pos,0);
	CLR(neg,0);
	for(int i=1;i<=n;i++) e[i].clear();
}

int main(){
	int Q,x,Case=0;
	while(~scanf("%d%d%d",&n,&m,&Q)){
		if(Case++) puts("");
		POS=0,NEG=0;
		for(int i=0;i<m;i++){
			scanf("%d%d%lld",&edge[i].u,&edge[i].v,&edge[i].val);
			if(edge[i].val==0){
				i--,m--;
				continue;
			}
			e[edge[i].u].push_back(i);
			e[edge[i].v].push_back(i);
			if(edge[i].val>0) POS+=edge[i].val; 
			else NEG+=edge[i].val;
		}
		limit=sqrt(m)+1;
		pretreat();
		while(Q--){
			char op[2];
			scanf("%s",op);
			if(op[0]=='Q'){
				scanf("%s",op);
				if(op[0]=='+') printf("%lld\n",POS);
				else if(op[0]=='-') printf("%lld\n",NEG);
				else printf("%lld\n",POS+NEG);
			}else{
				scanf("%d",&x);
				change(x);
			}
			if(POS<0 || NEG>0) return -1;
		}
		clear();
	}
	return 0;
}



ZOJ 3745 Salary Increasing

关键信息ri < li+1

那么直接暴力就好了,dp[i]记录i这个数有几个。

如果更新后的数字小于等于ri,那么这个数字就固定了,答案就加上它。

不过代码里我是全部更新好最后一起加的。


code:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <set>
#include <map>
using namespace std;
    
#define N  100100
#define ll long long
#define ALL(x)     x.begin(),x.end()
#define CLR(x,a)   memset(x,a,sizeof(x))
typedef pair<int,int> PI;
const int    INF=0x3fffffff;
const int    MOD=1000000007;
const double EPS=1e-9;


int dp[2*N];

int main(){
	int n,m,x;
	while(~scanf("%d%d",&n,&m)){
		CLR(dp,0);
		for(int i=0;i<n;i++){
			scanf("%d",&x);
			dp[x]++;
		}
		while(m--){
			int l,r,c;
			scanf("%d%d%d",&l,&r,&c);
			for(int i=r;i>=l;i--){
				if(i+c>r) dp[i+c]+=dp[i];
				else dp[i+c]=dp[i];
				dp[i]=0;
			}
		}
		ll ans=0;
		for(int i=1;i<2*N;i++) ans+=1ll*dp[i]*i;
	    printf("%lld\n",ans);	
	}
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值