Codeforces Global Round 1(solve7/8)

题目链接:https://codeforces.com/contest/1110

A题

题意:给你一个多项式,求最后是奇数还是偶数

思想:假如进制是偶数的话,只需要考虑最后*1的即可。进制是奇数的话,考虑下有多少位是奇数即可。

B题

题意:给你一个长度为m的杆子,有n个断了,然后最多粘粘k次,问你最小花费是多少,花费是粘连的距离和。

思想:先考虑每个点都粘上了,那么粘了n个点,然后在计算下任意2点之间的距离,只要最小的n-k个,表示这些点是拉长粘合的,其余都是单点。

C题

题意:q次询问,求max{ gcd(a&b,a^b) }  1<=b<a  (a已知)

思想:打表找规律即可。,除了2^k-1以外其他情况ans=2^p-1(其中p为满足ans>a的最小的数)

D题

题意:给出n个数,数的大小不超过m,问最多有多少个类似于(x,x,x),(x-1,x,x+1)这样的三元组,每个数最多用一次。

思想:DP,dp[i][j][k]代表i这个数有(i-1,i,i+1)这样的三元组j组 有 (i,i+1,i+2)这样的三元组k组。然后暴力枚举剩下的那种构成(i,i,i)这样的三元组即可。 所以dp转移方程就是dp[i][k][l]=max(dp[i][lk][l],dp[i-1][j][k]+(vis[i]-j-k-l)/3+l)

对照下上边的那个就可以理解状态是如何转移过来的,因为上一个的包括当前的2种情况。只需要通过那两种转移过来即可。

vis代表的是当前这个数出现几次。

#include<bits/stdc++.h> 
using namespace std;
const int maxn=1e6+10;
int vis[maxn];
int dp[maxn][3][3];
int main()
{
	int n,m,temp;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&temp);
		vis[temp]++;
	}
	memset(dp,-1,sizeof(dp));
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			dp[0][i][j]=0;
	for(int i=1;i<=m;i++)
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				for(int l=0;l<3;l++)
					if(vis[i]-j-k-l>=0)
						dp[i][k][l]=max(dp[i][k][l],dp[i-1][j][k]+(vis[i]-j-k-l)/3+l);					
	printf("%d\n",dp[m][0][0]);
	return 0;	
}

E题

题意:给你n个数,问你最后能否通过给的那种方法变成下边的哪些数。

思想:发现每次通过已知的方式进行计算的时候,只会交换相邻的差值。那么就求出来所有的差值,比较即可。同时比较第一个和最后一个即可,因为这两者不可交换。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll a[maxn];
ll b[maxn];
vector<ll>v1,v2;
int main() 
{
    ll n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		cin>>b[i];
	if(a[1]!=b[1] || a[n]!=b[n])
	{
		cout<<"No\n";
	}
	else
	{
		for(int i=2;i<=n;i++)
			v1.push_back(a[i]-a[i-1]);
		for(int i=2;i<=n;i++)
			v2.push_back(b[i]-b[i-1]);
		sort(v1.begin(),v1.end());
		sort(v2.begin(),v2.end());
		int flag=0;
		for(int i=0;i<v1.size();i++)
		{
			if(v1[i]!=v2[i])
				flag=1;
		} 
		if(flag)
			cout<<"No\n";
		else
			cout<<"Yes\n";
	}
    return 0;
}

F题

题意:给你一棵树,然后问你u作为根,区间【l,r】最近的叶子到u的距离。1不是叶子节点。

思想:线段树换根。先离线将所有的查询记录,然后从1开始dfs进行线段树换根。

思考如何换根,刚开始先算出来所有叶子节点到达1的距离,构建出来这样的一颗线段树,然后我们思考如果根下移会造成什么影响,我们发现从u到v的过程,v的孩子节点到达v的距离等于到达u的减去uv之间的距离,然后v之外的到达v的节点先通过u在到达v,那么就多了一个uv之间的距离,所以我们dfs的同时,同时先让整个区间增加距离,然后v的控制区间减少-2倍的距离即可,最后访问完子树在加回来。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=500005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,ll> PI;
typedef pair<pair<int,int>,int> PII;
vector<PI>v[maxn];
vector<PII>vv[maxn]; 
int in[maxn];
int out[maxn];
ll a[maxn];
ll ans[maxn];
int cnt,n;
struct node{
	ll Min;
	ll lazy;
}no[maxn<<2];
void dfs(int u,ll ans)//欧拉序 
{
	in[u]=++cnt;
	if(v[u].size())
		a[cnt]=inf;
	else
		a[cnt]=ans;	
	for(PI v1 : v[u])
	{
		dfs(v1.first,ans+v1.second);
	}
	out[u]=cnt;
}
void pushup(int id)
{
	no[id].Min=min(no[id<<1].Min,no[id<<1|1].Min);
}
void pushdown(int id)
{
	no[id<<1].lazy+=no[id].lazy;
	no[id<<1|1].lazy+=no[id].lazy;
	no[id<<1].Min+=no[id].lazy;
	no[id<<1|1].Min+=no[id].lazy;
	no[id].lazy=0;
}
void build(int id,int l,int r)
{
	no[id].lazy=0;
	if(l==r)
	{
		no[id].Min=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	pushup(id);
}
void update(int id,int l,int r,int L,int R,ll w)
{
	if(l>=L && r<=R)
	{
		no[id].lazy+=w;
		no[id].Min+=w;
		return ;
	}
	if(no[id].lazy)
		pushdown(id);
	int mid=(l+r)>>1;
	if(L<=mid)
		update(id<<1,l,mid,L,R,w);
	if(R>mid)
		update(id<<1|1,mid+1,r,L,R,w);
	pushup(id);
}
ll query(int id,int l,int r,int L,int R)
{
	if(l>=L && r<=R)
		return no[id].Min;
	if(no[id].lazy)
		pushdown(id);
	int mid=(l+r)>>1;
	ll ans=inf;
	if(L<=mid)
		ans=query(id<<1,l,mid,L,R);
	if(R>mid)
		ans=min(ans,query(id<<1|1,mid+1,r,L,R));
	return ans; 
}
void Dfs(int u,ll valu)
{
	update(1,1,n,1,n,valu);
	update(1,1,n,in[u],out[u],-2*valu);
	for(PII x : vv[u])
	{
		ans[x.second]=query(1,1,n,x.first.first,x.first.second);
	}
	for(PI x : v[u])
	{
		Dfs(x.first,x.second);
	}
	update(1,1,n,1,n,-valu);
	update(1,1,n,in[u],out[u],2*valu);
}
int main()
{
	int q,p,l,r;
	ll w;
	scanf("%d%d",&n,&q);
	for(int i=2;i<=n;i++)
	{
		scanf("%d%lld",&p,&w);
		v[p].push_back(PI(i,w));
	}
	dfs(1,0ll);
	build(1,1,n);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&w,&l,&r);
		vv[w].push_back(make_pair(make_pair(l,r),i));
	} 
	Dfs(1,0);
	for(int i=1;i<=q;i++)
		printf("%lld\n",ans[i]);
	return 0;
} 

G题

题意:给你一棵树,有一部分点(可能为0个)初始为白色,然后白手先,黑手后,最后谁先组成三个相连(树上相连)的就赢了。

思想来自:http://www.cnblogs.com/Itst/p/10356243.html   https://www.cnblogs.com/TinyWong/p/10391591.html

思想:不可能输,思考下,除了第一步,每一步都堵死黑手,是不可能输的。

思考下如何赢:刚开始我们认为都是无色的

① 那么当一个节点有>=4个孩子的时候必赢----5个点先手怎么都能连上3个点。

② 对于任何节点都只有一个孩子节点,或者有2个孩子节点的时候,当只有一个的时候染了父亲,另外一个肯定染孩子,有2个时候,你染了父亲,我染了孩子,另外一个也够不成三个连续的,所以肯定是平局。

③ 当一个节点有三个孩子的时候,假如有2个孩子不是叶子节点也是必赢的。染父亲节点和任意2个孩子节点或者父亲节点和孩子节点和孩子的孩子节点。

④ 三个孩子节点的还有2种情况,分别是一头带一个这样的时候,无论你怎么染色,肯定是平局。

 

还有一种就是这样的情况,这样的也分2种,分他们之间是有奇数个点还是偶数个点,有详细的描述在上边2个连接,可以看下。

所以只需要考虑所有点的度即可。

那么上边都是无色的时候,现在有一部分点是白色的,该如何将其转换呢。

我们将白色的点再加上三条边。

A是我们刚开始的白色点,然后分析你是如何染色,假如我染B肯定完犊子,所以我染A,那么另外一个人肯定染B,CD就无所谓了。所以白色的点就可以抽象成这样相等于A下边加了一个度为3的节点,这个就跟③扯上关系了。

最后如果有2个三头的话,只需要判断n奇偶就行。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
vector<int>v[maxn];
int du[maxn];
int main()
{
	int t,n,uu,vv,flag;
	char str[maxn];
	scanf("%d",&t);
	while(t--)
	{
		flag=0;
		scanf("%d",&n);
		for(int i=0;i<=2*n;i++)
		{
			du[i]=0;
			v[i].clear();
		}
		for(int i=0;i<n-1;i++)
		{
			scanf("%d%d",&uu,&vv);
			v[uu].push_back(vv);
			v[vv].push_back(uu);
			du[vv]++;
			du[uu]++;
		}
		scanf("%s",str);
		if(n<3)
		{
			puts("Draw");
			continue ;
		}
		if(n==3)
		{
			int ans=0;
			for(int i=0;i<n;i++)
				if(str[i]=='W')
					ans++;
			if(ans>=2)
				puts("White");
			else
				puts("Draw"); 
			continue;
		}
		for(int i=0;i<n;i++)
		{
			if(str[i]=='W')
			{
				++n;
				v[i+1].push_back(n);
				v[n].push_back(i+1);
				du[i+1]++;
				du[n]=3;
			}
		}
		int sum=0;
		for(int i=1;!flag && i<=n;i++)
		{
			if(du[i]>=4)
				flag=1;
			else if(du[i]==3)
			{
				int ans=0;
				for(int j=0;j<v[i].size();j++)
				{
					if(du[v[i][j]]>=2)
						ans++;
				}
				if(ans>=2)
					flag=1;
				sum++;
			}
		}
		if(sum==2 && (n&1))
			flag=1;
		if(flag)
			puts("White");
		else
			puts("Draw");
	}
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值