首页 > 其他分享 >Codeforces 1444E - Finding the Vertex

Codeforces 1444E - Finding the Vertex

时间:2023-05-26 21:22:51浏览次数:50  
标签:标号 int Vertex Codeforces ec MAXN Finding 考虑 hd

非常神秘的一道题,当之无愧的 *3500。

首先考虑转化题意。考虑一种决策树,由于我们每次问一条边之后,相当于会根据信息删掉两个连通块中的一个,因此一种决策树实际上对应了原树的一棵边分树。而为了让最坏情况下的询问次数最少,我们目标实际上是最小化边分树的深度。

考虑借鉴 P5912 JAS 的思路,我们相当于是要给每条边一个标号 \(c_e\),使得任意两个标号相等的边之间至少存在一个编号比它们都小的边,要求最小化最大标号。首先记最大标号为 \(M\),先将每条边标号变为 \(M-c_e\),这样限制变为两个标号相等的边之间至少存在一个编号比它们大的边——之所以这样做是为了方便后续的 DP 过程。考虑对于每个点维护一个集合 \(S_x\),对于一种权值 \(v\),其属于 \(S_x\) 当且仅当 \(x\) 子树内存在一条权值为 \(v\) 的边满足其到 \(x\) 路径不存在编号 \(>v\) 的边。我们再记 \(f_x=\sum\limits_{v\in S_x}2^v\)。

考虑怎么用儿子们的 \(f_y\) 合并得到 \(f_x\),设 \(y\) 与父亲连边的权值为 \(w_y\),那么如果 \(w_y\in S_y\) 就寄了,说明此种标号分配方案不合法。否则添加 \(y\) 与父亲的边相当于删掉 \(S_y\) 中所以 \(<w_y\) 的数,同时加入 \(w_y\),我们记新得到的集合对应的二进制数为 \(g_y\),那么限制是所有儿子的 \(g_y\) 两两 and 起来等于 \(0\),此时父亲的 \(f_x\) 等于儿子的 \(f_y\) 之 or。

发现一个性质,就是把 \(f\) 看成二进制数之后,\(f_x\) 肯定是越小越好,因为根节点的 \(f\) 肯定是越小越好,而对于一个非根节点,考虑两个二进制数 \(A<B\),如果儿子节点的 \(f\) 值为 \(A\),那么我们在添加其与父亲边的时,令其权值为最大的在 \(B\) 中出现但在 \(A\) 中没出现的数,这样进行这次转移之后 \(A\) 对应的二进制数就会成为 \(B\) 的子集,而 \(B\) 操作之后得到的 \(f\) 值肯定会变大,这样无论如何,\(B\) 对应的父亲节点的 DP 值肯定比 \(A\) 大,一路归纳下去即可得证。

于是问题转化为,你要给每个儿子给父亲的边附上合适的边权 \(w_y\),使得 \(\text{trs}(f_y,w_y)\) 两两不交且它们的并最小,其中 \(\text{trs}(f_y,w_y)\) 为将 \(f_y\) 中 \(<w_y\) 的位都变为 \(0\) 且第 \(w_y\) 位变为 \(1\) 后的结果。注意到这个 \(\text{trs}(f_y,w_y)\) 其实有点烦,但是咱大胆一点,把它变为“找到一个 \(g_y>f_y\)”,发现实际上是等价的!因为考虑任意一个 \(v>f_y\) 的 \(v\),找到最高的那一位 \(v\) 与 \(f_y\) 不同的位,设为 \(b\),那么显然 \(\text{trs}(f_y,b)\) 是 \(v\) 的子集,肯定优于 \(v\)。

于是树形 DP 的过程中需要解决这样一个问题:

给定 \(c\) 个 \(<2^n\) 的数 \(f_1,f_2,\cdots,f_c\),要求出一组 \(g_1,g_2,\cdots,g_c\) 使得 \(g_i>f_i\),\(i\ne j\Rightarrow g_i\cap g_j=0\) 且 \(\sum g_i\) 最小。

考虑按位决策,先令答案的每一位都是 \(1\),然后依次枚举每一位,看看能否将它改成 \(0\),考虑怎么 check 是否有可能 \(\sum g_i\subseteq V\),从高到低枚举每一位,按位决策每个 \(g_i\) 的位,维护一个集合表示当前 \(g_i\) 贴着下界的下标集合 \(S\),然后分情况讨论:

  • 如果 \(V\) 的当前位为 \(0\),那么如果当前 \(S\) 中存在一个 \(f_i\) 的当前位为 \(1\) 就寄了,否则 \(S\) 保持不变。
  • 如果 \(V\) 的当前位,那么我们肯定会贪心地将这一位上的 \(1\) 扔给某个 \(g_i\),考虑怎么扔:
    • 如果有超过 \(2\) 个 \(f_i\) 当前位为 \(1\),那么寄。
    • 如果恰有一个 \(f_i\) 当前位为 \(1\),那么只能扔给这个 \(f_i\),\(S\) 保持不变。
    • 如果所有 \(f_i\) 当前位都是 \(0\),那么肯定贪心扔给 \(f\) 最大的,同时将这个最大的元素从 \(S\) 中删除。

最后检查 \(S\) 是不是空即可。

算下这个做法的复杂度,\(\sum c=O(n)\) 有一个 \(n\),按位决策需要遍历每一位,有一个 \(n\),check 时候又要遍历 \(n\),选最大的 \(f\) 使用堆维护,比较的时候可以 bitset 优化到 \(\dfrac{n}{\omega}\),总共是 \(\dfrac{n^4\log n}{\omega}\)。

const int MAXN=100;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
struct lnum{
	bitset<MAXN+5>a;
	lnum(){a.reset();}
	friend bool operator <(const lnum &X,const lnum &Y){
		int pos=(X.a^Y.a)._Find_first();
		if(pos==X.a.size()||X.a[pos])return 0;
		return 1;
	}
}f[MAXN+5],af[MAXN+5],g[MAXN+5];
int val[MAXN+5],tmp[MAXN+5],len;
bool check(lnum v){
	priority_queue<pair<lnum,int> >q;
	for(int i=1;i<=len;i++)q.push(mp(af[i],i)),g[i].a.reset();
	for(int i=0;i<n;i++){
		if(!v.a[i]){
			if(!q.empty()&&q.top().fi.a[i])return 0;
		}else{
			auto t=q.top();q.pop();
			if(t.fi.a[i]){
				if(!q.empty()&&q.top().fi.a[i])return 0;
				t.fi.a[i]=0;g[t.se].a[i]=1;
				q.push(t);
			}else g[t.se].a[i]=1;
		}
		if(q.empty())return 1;
	}return 0;
}
void dfs(int x,int fa){
	for(int e=hd[x];e;e=nxt[e])if(to[e]!=fa)dfs(to[e],x);
	len=0;
	for(int e=hd[x];e;e=nxt[e])if(to[e]!=fa)af[++len]=f[to[e]];
	for(int i=0;i<n;i++)f[x].a[i]=1;
	for(int i=0;i<n;i++){
		f[x].a[i]=0;
		if(!check(f[x]))f[x].a[i]=1;
	}
	check(f[x]);int cur=0;
	for(int e=hd[x];e;e=nxt[e])if(to[e]!=fa){
		++cur;
		for(int j=0;j<n;j++)if(g[cur].a[j]&&!af[cur].a[j]){
			val[e>>1]=n-1-j;break;
		}
	}
}
bool ban[MAXN+5],has[MAXN+5];pii mx=mp(0,0);
void dfs_cmp(int x){
	if(has[x])return;has[x]=1;
	for(int e=hd[x];e;e=nxt[e])if(!ban[e>>1])chkmax(mx,mp(val[e>>1],e>>1)),dfs_cmp(to[e]);
}
int main(){
	scanf("%d",&n);
	for(int i=1,u,v;i<n;i++)scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs(1,0);int pre=1,res=0;
	for(int i=1;i<n;i++)chkmax(res,val[i]+1);
//	printf("res=%d\n",res);
	while(1){
		memset(has,0,sizeof(has));mx=mp(0,0);dfs_cmp(pre);
		if(!mx.se){cout<<"! "<<pre<<endl;break;}
		cout<<"? "<<to[mx.se<<1]<<" "<<to[mx.se<<1|1]<<endl;
		int x;cin>>x;pre=x;ban[mx.se]=1;
	}
	return 0;
}
/*
12
8 6
8 12
4 3
5 11
7 1
1 2
4 10
3 5
4 7
7 8
12 9
*/

标签:标号,int,Vertex,Codeforces,ec,MAXN,Finding,考虑,hd
From: https://www.cnblogs.com/tzcwk/p/Codeforces-1444E.html

相关文章

  • Educational Codeforces Round 149 (Rated for Div. 2) 题解
    https://codeforces.com/contest/1837https://codeforces.com/contest/1837/problems利益相关:上紫祭。真的不要以为这道题放在F就不敢做。压线过题的感觉真好。ABC题都过水,就不写了。代码丢在这里:A:https://codeforces.com/contest/1837/submission/207156920B:https:......
  • CodeForces 1107B Digital root(找规律)
    传送门每个数字都有个数位和,就是把数字的每一位相加直到数位和是一个个位数。然后题目就要你求第K个数位和为X的数字是多少。写一些数字出来就很容易发现规律了可以看出每一竖列的数位和是相等的,然后就找到规律是9*(k-1)+x,注意数据范围是1e12,是longlong,然后就这么多,就可以直......
  • CodeForces 1107A Digits Sequence Dividing(思维)
    传送门唉,题目讲的天花乱坠的,花里胡哨,一上来真是把我唬住了。愣了半天也没看出来到底咋做,后来借助翻译明白了这个题就是让你把一串字符分成两串,然后第一串要比第二串小,就这样,然后又是个SpecialJudge。做的时候就把第一个数作为第一个串,然后串长如果为2,就判断一下后面的串要比第一个......
  • CodeForces 1108B Divisors of Two Integers(思维)
    传送门题目大意就是给你由X,Y两个数的所有因子(包括一和数本身)组成的序列,然后通过这个序列找出这两个数。由此可见,序列里最大的数一定是X或Y其中的一个,然后我们的任务就是找另一个了,我找的是剩下的因子里不能被已找到的那个数整除的数中最大的数,且没有和这个数相同的数。#include<std......
  • Educational Codeforces Round 63 (Rated for Div. 2) A,B,C
    A.ReverseaSubstring传送门就是找不满足升序排列的字母,输出就行了。#include<bits/stdc++.h>#definelllonglongusingnamespacestd;constintmaxn=3e5+10;chars[maxn];intmain(){#ifndefONLINE_JUDGEfreopen("in","r",stdin);#endif//ONL......
  • CodeForces 1105B Zuhair and Strings(思维 + 枚举)
    传送门题目大意就是给你一个字符串,还有一个等级K,K的具体含义就是连续的相同的字符串的个数,题目就是要求长度为k的,字符一样的子串有几个,如果k==2就是比如aa,bb,cc,dd,..... 这样的,注意不能重叠。因为题目给的数据范围在2e5,所以枚举从a到z,然后取最大值就好了。代码如下#incl......
  • Codeforces Round #553 (Div. 2) A,B,C,D
    A:MaximandBiology传送门因为数据范围比较小,就暴力就完事儿了。#include<bits/stdc++.h>usingnamespacestd;constintINF=0x3f3f3f3f;intconvert(chara,charb){if(a==b)return0;if(a>b)swap(a,b);intx=b-a;a=......
  • CodeForces 1108C Nice Garland(DFS)
    传送门题目大意就是给你一个由'R','G','B'三个字母组成的字符串,然后这个字符串需要满足任意相邻的三个字符不能相同,如果不满足则需要你对其进行更改,然后输出更改的次数和更改完的字符串。具体的思路就是枚举"RGB"这三个的组合,显然只有3!个,然后依次计算步数代码如下#include<iostream>......
  • Codeforces Round #552 (Div. 3) 1154
    A:传送门就是解个方程,也没什么说的#include<bits/stdc++.h>usingnamespacestd;intmain(){inta[4],x,b,c;for(inti=0;i<4;i++)cin>>a[i];sort(a,a+4);c=a[3]-a[0];x=a[1]-c;b=a[2]-c;cout<<x......
  • Educational Codeforces Round 149 (Rated for Div. 2)
    EducationalCodeforcesRound149(RatedforDiv.2)A-GrasshopperonaLine思路:只有两种情况,x整除k时为x-1和1,否则为xvoidsolve(){intx,k;cin>>x>>k;if(x%k==0){cout<<"2\n"<<x-1<<&qu......