题目链接: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;
}