Codeforces Round #746 (Div. 2)

A.贪心

很简单,不能连续的放同一个

那么我肯定是最大攻击力和次大攻击力的武器交替使用

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
inline int read(){
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
void write(int num)
{
	if (num/10)write(num/10);
	putchar('0'+num%10);
}
int n,h;
int num1,num2,num;
int main()
{
	int T=read();
	while (T--)
	{
		n=read();h=read();
		num1=read();num2=read();
		if (num1>num2)swap(num1,num2);
		for (int i=2;i<n;++i)
		{
			num=read();
			if (num>num1)num1=num;
			if (num1>num2)swap(num1,num2);
		}
		write(h/(num1+num2)*2+(h%(num1+num2)+num2-1)/num2);puts("");
	}
}

B.思维

短暂的思考,我们注意到数组中可能存在一些无法动弹的数字

即,对于一些索引 i i i i − 1 < x , n − i < x i-1<x,n-i<x i1<x,ni<x

因此这些索引一开始就应当站在正确的位置上

而对于其他可以动弹的索引,可以利用两端点,自由交换

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
int a[maxn],b[maxn];
int n,x;
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>x;
		for (int i=1;i<=n;++i)cin>>a[i],b[i]=a[i];
		sort(b+1,b+1+n);
		string ans = "YES\n";
		for (int i=1;i<=n;++i)if (i-1<x&&n-i<x&&a[i]!=b[i])
		{
			ans = "NO\n";
			break;
		}cout<<ans;
	}
}

C.思维

很好的思维题

考虑 ⊕ ​ \oplus​ 操作的特性

  • 当整棵树的 ⊕ \oplus 和为 0 0 0,那么随便断掉一个叶子节点即可满足条件
  • 当中整棵树的 ⊕ \oplus 和不为 0 0 0,记为 t a r tar tar,断边后的每一个连通分量 ⊕ \oplus 和都为 t a r tar tar

考虑第二种情况,如果选取了合适的节点作为根:

被红框圈起来的子树,其 ⊕ \oplus 和为 t a r tar tar

我们可以通过断 3 − 7 , 2 − 5 3-7,2-5 37,25两条边实现目标

最多使用两次断边

但是,考虑到我么每次都默认以 1 1 1为根,可能出现这种情况:

如图, ⊕ \oplus 和为 t a r tar tar的子树跨越了根节点 1 1 1

我们观察到,满足这种情况下,节点 7 7 7 ⊕ \oplus 和为 0 0 0,且节点 7 7 7含有一棵子树 ⊕ \oplus 和为 t a r tar tar

因此进行 d f s ( u ) dfs(u) dfs(u),计算每个节点的 ⊕ \oplus x o r [ u ] xor[u] xor[u],同时判断以该节点为根的子树中是否存在子树 ⊕ \oplus 和为 t a r tar tar h a s [ u ] has[u] has[u]

如果存在两个儿子 v 1 , v 2 v_1,v_2 v1,v2 h a s [ v 1 ] = = h a s [ v 2 ] = = 1 has[v_1]==has[v_2]==1 has[v1]==has[v2]==1 那么 Y E S YES YES

如果 x o [ u ] = = 0 xo[u]==0 xo[u]==0且存在儿子 v v v使得 h a s [ v ] = = 1 has[v]==1 has[v]==1同样 Y E S YES YES

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
vector<int> G[maxn];
int tar,a[maxn];
bool has[maxn];
int xo[maxn];
int n,k;
bool dfs(int u,int fa)
{
	xo[u]=a[u];has[u]=0;
	int cnt=0;
	for (int v:G[u])if (v!=fa)
	{
		if (dfs(v,u))return true;
		xo[u]^=xo[v];
		if (has[v])++cnt;
		if (xo[v]==0&&has[v])return true;
	}
	if (xo[u]==tar)has[u]=1;
	if (cnt>0)has[u]=1;
	if (cnt>=2)return true;
	return false;
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>k;tar=0;
		for (int i=1;i<=n;++i)G[i].clear();
		for (int i=1;i<=n;++i)cin>>a[i],tar^=a[i];
		for (int i=1,u,v;i<n;++i)
		{
			cin>>u>>v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		if (tar==0)cout<<"YES\n";
		else
		{
			if (k>=3&&dfs(1,0))cout<<"YES\n";
			else cout<<"NO\n";
		}
	}
}

D.思维+二分

稍微思考一下,其实题目要求我们找到最大的那一条边

每次的询问也是返回所有点路径上的最大边的权值

既然如此,我们自然想要对边进行二分

观察数据范围,查询最多 12 12 12次,一共 1 e 3 1e3 1e3条边,很合理

但是,这时出现了一个问题。我们该如何二分边呢?

不能简单的将所有便存储起来,然后每次二分一半

以样例为为例,会出现这种现象:

此刻我们有边 ( 1 , 2 ) , ( 1 , 5 ) , ( 2 , 3 ) , ( 2 , 4 ) , ( 5 , 6 ) (1,2),(1,5),(2,3),(2,4),(5,6) (1,2),(1,5),(2,3),(2,4),(5,6)

分一半为 ( 1 , 2 ) , ( 1 , 5 ) , ( 2 , 3 ) (1,2),(1,5),(2,3) (1,2),(1,5),(2,3) ( 2 , 4 ) , ( 5 , 6 ) (2,4),(5,6) (2,4),(5,6)

观察 ( 2 , 4 ) , ( 5 , 6 ) (2,4),(5,6) (2,4),(5,6)我们发现这些边有 4 4 4个点,如果我们查询 2 , 4 , 5 , 6 2,4,5,6 2,4,5,6的话

那么实际上返回的是 ( 2 , 4 ) , ( 2 , 1 ) , ( 1 , 5 ) , ( 5 , 6 ) (2,4),(2,1),(1,5),(5,6) (2,4),(2,1),(1,5),(5,6)中最大的值

与我们想法不一样

因此,这里的二分是有讲究的

我们二分出来的边,这些边要能够造成一棵树!

即将边中出现的所有点完全连通

只有这样才能避免出现上述的情况

这里给出一种比较简单实现的方案

我们按照 d f s dfs dfs的顺序添加边,有所不同的是回溯的边也要添加

可能会有边被重复添加了,但是可以保证每一次二分的时候,两边的边都可以联通

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1100;
array<int,2> res;
int ans;
int n;
int query(vector<array<int,2>>& es)
{
	set<int> se;
	for (array<int,2> ar:es)se.insert(ar[0]),se.insert(ar[1]);
	cout<<"? "<<se.size();
	for (int u:se)cout<<" "<<u;
	cout<<endl;
	cout.flush();
	int ret;cin>>ret;
	cout.flush();
	return ret;
}
void solve(vector<array<int,2>> es)
{
	if (es.size()==1)
	{
		res = es[0];
		return;
	}
	vector<array<int,2>> nxt;
	while (es.size()>nxt.size())
	{
		nxt.push_back(es.back());
		es.pop_back();
	}
	if (query(es)==ans)solve(es);
	else solve(nxt);
}
vector<array<int,2>> edges;
vector<int> G[maxn];
void dfs(int u,int fa)
{
	for (int v:G[u])if (v!=fa)
	{
		edges.push_back({u,v});
		dfs(v,u);
		edges.push_back({v,u});
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin>>n;
	for (int i=1,u,v;i<n;++i)
	{
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	ans = query(edges);
	solve(edges);
	cout<<"! "<<res[0]<<" "<<res[1]<<endl;
}

E.思维 +前缀和

何时 a l & a l + 1 & a l + 2 & , … , & a r > a l ⊕ a l + 1 ⊕ a l + 2 ⊕ , … , ⊕ a r a_l \& a_{l+1} \& a_{l+2} \& , \dots,\& a_r>a_l \oplus a_{l+1}\oplus a_{l+2}\oplus,\dots,\oplus a_r al&al+1&al+2&,,&ar>alal+1al+2,,ar

位运算的问题一般都是按位看,从高到低看

我们注意到,倘若 A N D AND AND和与 X O R XOR XOR和在前 k − 1 k-1 k1位都相同,在 k − 1 k-1 k1位出现不同

此时 A N D AND AND在第 k k k位为 1 1 1 X O R XOR XOR 0 0 0 (从高位向地位看)

首先考虑 A N D AND AND在第 k k k位为 1 1 1,那么 [ l , r ] [l,r] [l,r]上所有的数第 k k k位都为 1 1 1

再考虑, X O R XOR XOR k k k位为 0 0 0,那么 [ l , r ] [l,r] [l,r]上第 k k k位为 1 1 1的数有偶数个

综上,我们得出结论 [ l , r ] [l,r] [l,r]的长度一定为偶数,且前 k − 1 k-1 k1 A N D AND AND X O R XOR XOR都为 0 0 0

预处理前缀亦或和数组 p r e [ i ] pre[i] pre[i]

我们枚举 k k k,从 0 0 0 21 21 21

枚举 r r r,找最远的符合条件的 l l l

首先, [ l , r ] [l,r] [l,r]为偶数长度这一目标很容易实现

但是,如何保证前 k − 1 k-1 k1位都是 0 0 0呢?

利用前缀亦或和数组 p r e [ i ] pre[i] pre[i]

p r e [ l ] pre[l] pre[l] k − 1 k-1 k1 p r e [ r ] pre[r] pre[r] k − 1 k-1 k1位相同的集合里搜寻

同时要满足 [ l , r ] [l,r] [l,r] k k k位都是 1 1 1,这个可以利用双指针实现

#include<bits/stdc++.h>
using namespace std;
list<int> mp1[1<<21],mp2[1<<21];
int pre[1<<21];
int n;
int main()
{
	ios::sync_with_stdio(0);
	cin>>n;
	for (int i=1;i<=n;++i)cin>>pre[i],pre[i]^=pre[i-1];
	int ans = 0;
	for (int k=0;k<=21;++k)
	{
		mp2[0].push_back(0);
		int l = 1;
		for (int i=1;i<=n;++i)
		{
			if (!((pre[i]^pre[i-1])&1))l=i+1;
			int id = pre[i];
			if (i&1)
			{
				while (!mp1[id].empty()&&mp1[id].front()+1<l)mp1[id].pop_front();
				if (!mp1[id].empty())ans = max(ans,i-mp1[id].front());
				mp1[id].push_back(i);
			}
			else
			{
				while (!mp2[id].empty()&&mp2[id].front()+1<l)mp2[id].pop_front();
				if (!mp2[id].empty())ans = max(ans,i-mp2[id].front());
				mp2[id].push_back(i);
			}
		}
		for (int i=1;i<=n;++i)
		{
			mp1[pre[i]].clear();
			mp2[pre[i]].clear();
			pre[i]>>=1;
		}mp1[0].clear();mp2[0].clear();
	}cout<<ans<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值