D1/2. Tree Queries和香港站E

D. Tree Queries

https://codeforces.com/problemset/problem/1695/D2
题意:给定一棵树,有一个隐藏节点x,你可以查询一些节点,可以得到询问点到x的距离,问最少询问次数使得对于任意隐藏点都可以找到。
D1 n<=2000 D2 n<=2e5
题解:
思路1(dp):我们令f[w]为在w点或以外有询问点时隐藏点在w子树时所需的最少询问次数,则f[w]=∑f[v]+max(cnt-1,0),cnt为无询问点的子树个数(因为我们首先需要确定x在哪个子树中,故需要知道外部到子树根的距离和至多有一个子树中无询问点),此时dp即可。但对于不同的根节点,dp所得的答案有所不同,故我们可以枚举根节点,然后dp,答案再加上对于根节点的询问,复杂度n2可以通过D1。
对于D2,瓶颈在于枚举根,而由于我们除了对根的询问外,每个询问都做到了最小化询问次数,故对于每个根的不同情况只能是在于对根的询问是多余的,我们进一步发现,对于度数>=3的根,总有点在子树外,此时不需对根询问,即可O(n)完成。
思路2(转化问题寻找性质):对于k个询问点,每个点可以用一个k维向量表示,其中每一维表示到第i个询问点的距离,我们题目即要求找到最小的k使得任意两点向量两两不同。对一个点,什么时候会有相同状态?若有其两个子树中均无询问点,那么会产生相同状态,即对于任意一个点,其至多有一个子树无询问点。实现可以采用从叶子节点向上走直到走到一个度数>=3的点时才停下并标记那个点,最终结果即为叶子节点个数-标记点数。复杂度O(n)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
vector<int> g[N];
int v[N],p[N];
void solve(){
	int n;cin>>n;
	for(int i=1;i<=n;i++) g[i].clear(),v[i]=0,p[i]=0;
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	if(n==1){
		cout<<0<<endl;return;
	}
	int ok=0,cnt=0;
	for(int i=1;i<=n;i++){
		if(g[i].size()>2) ok=1;
	}
	if(!ok){
		cout<<1<<endl;
		return;
	}
	for(int i=1;i<=n;i++){
		if(g[i].size()==1){
			cnt++;
			int x=g[i][0],pre=i;
			while(1){
				if(p[x]) break;
				p[x]=1;
				if(g[x].size()>2){
					v[x]=1;break;
				}
				else{
					for(auto it:g[x]){
						if(it==pre) continue;
						pre=x,x=it;break;
					}
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(v[i]) cnt--;
	}
	cout<<cnt<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--) solve();
}

E. Goose, Goose, DUCK?

https://codeforces.com/gym/104172/problem/E
题意:问有多少个子段满足没有任何一个数的数量正好等于k。n,k<=1e6
题解:想要优化复杂度,可以依靠前面的状态经过较少的转移得到。我们可以枚举右端点r,问对于每个右端点有多少左端点满足条件,对于每次移动右端点,只会改变当前位置的区间,假设当前r所在点a[r]=x,则其不满足条件的l的区间从[prel,l]->[l,nexl],
使用线段树维护可以O(logn)转移,即使得不满足的区间全部-1,每次转移对于上次的区间全部+1,变化后的区间全部减1,则满足条件的左端点l只能位于值为0的点上,即区间查询值为MAX(0)的数量即可。

posted @ 2023-05-26 22:17  wrong,anser  阅读(17)  评论(0)    收藏  举报