NOIP模拟5

A. 战争

做法

  1. 把影响到的点和岛上另外一个点拿出来,然后如果一个岛屿只有一个关键点,就不用另外一个点,跑 BK, 或者折半

  2. 2k 枚举,删边的钦定一段不选,加边的钦定两端必选,check 合法性后构造方案

我使用的第二种,因为学不会 BK

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 100005;
int p, n, c[maxn], k, k1, k2;
vector<pii>link, cut;
vector<int>vec, oth, ans;
set<int>part[maxn];
bool del[maxn], vis[maxn], app[maxn];
vector<int> node[maxn];
int edge[maxn], count[maxn], now;
int siz[maxn];
void Del(int x){
	if(!del[x])--siz[c[x]];
	del[x] = true;
}
void Add(int x){
	if(del[x])++siz[c[x]];
	del[x] = false;
}
int find(int p){
	for(int x : part[p])if(!del[x])return x;
}
int main(){
	freopen("war.in","r",stdin);
	freopen("war.out","w",stdout);
	p = read(), n = read();	
	for(int i = 1; i <= n; ++i)part[c[i] = read()].insert(i);
	k = read();
	for(int i = 1; i <= k; ++i){
		int u = read(), v = read();
		if(!vis[c[u]]){vis[c[u]] = true; vec.push_back(c[u]);}
		if(!vis[c[v]]){vis[c[v]] = true; vec.push_back(c[v]);}
		if(c[u] == c[v])link.push_back(pii(u, v)), ++k2;
		else{
			cut.push_back(pii(u, v)), ++k1;
			if(part[c[u]].count(u))part[c[u]].erase(u);
			if(part[c[v]].count(v))part[c[v]].erase(v);
		}
	}
	for(int i = 1; i <= p; ++i)if(!vis[i] && part[i].size()){oth.push_back(*part[i].begin());}
	for(int v : vec)while(part[v].size() > 1)part[v].erase(part[v].begin());
	for(pii v : cut)part[c[v.first]].insert(v.first),part[c[v.second]].insert(v.second);
	for(int i : vec)siz[i] = part[i].size();
	for(ll i = 0; i < (1ll << k1); ++i){
		for(int j = 0; j < k1; ++j)
			if((1ll << j) & i)Del(cut[j].first);
			else Del(cut[j].second);
		for(ll j = 0; j < (1ll << k2); ++j){
			for(int p = 0; p < k2; ++p)app[link[p].first] = app[link[p].second] = false;
			for(int p : vec)node[p].clear(), edge[p] = 0;
			for(int p = 0; p < k2; ++p)if((1ll << p) & j){
				if(del[link[p].first] || del[link[p].second])goto X;
				++edge[c[link[p].first]];
				if(!app[link[p].first]){app[link[p].first] = true; node[c[link[p].first]].push_back(link[p].first);}
				if(!app[link[p].second]){app[link[p].second] = true; node[c[link[p].second]].push_back(link[p].second);}
			}
			for(int p : vec)if(node[p].size() * (node[p].size() - 1) != edge[p] * 2)goto X;
			now = 0;
			for(int p : vec)
				if(node[p].size())now += node[p].size();
				else if(siz[p]) ++now;
			if(now > ans.size()){
				ans.clear();
				for(int p : vec)
					if(node[p].size())for(int x : node[p])ans.push_back(x);
					else if(siz[p])ans.push_back(find(p));
			}
			X:;
		}
		for(ll j = 0; j < k1; ++j)
			if((1ll << j) & i)Add(cut[j].first);
			else Add(cut[j].second);
	}
	printf("%d\n",oth.size() + ans.size());
	for(int x : oth)printf("%d ",x);
	for(int x : ans)printf("%d ",x);
	printf("\n");
	return 0;
}

B. 肥胖

发现对于最优的策略,一定存在一种方案先吃完最小边一侧的点,再去另一边

于是断开最小边,可以划分成子问题

直接干肯定不行,于是考虑

建出最大生成树的克鲁斯卡尔重构树,然后进行 DP

fx=min(valsumu,fvsumu)

为什么不用跟 fu 取 $min ? ,一种解释是 fu>=valsum 但是我觉得证明不是很严谨

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 400005;
int n, m;
int val[maxn], c[maxn], cnt;
struct DSU{
	int f[maxn];
	void init(){for(int i = 1; i <= n + n; ++i)f[i] = i;}
	int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
}S;
struct EDGE{
	int u, v, w;
	friend bool operator < (const EDGE &x, const EDGE &y){
		return x.w > y.w;
	}
}E[maxn];
int ans, lson[maxn], rson[maxn];
ll sval[maxn], f[maxn];
void dfs(int x, int fa){
	if(x <= n){sval[x] = c[x]; f[x] = INT_MAX; return;}
	dfs(lson[x], x);
	dfs(rson[x], x);
	sval[x] += sval[lson[x]];
	sval[x] += sval[rson[x]];
//	f[x] = max(min({f[lson[x]], val[x] - sval[lson[x]], f[rson[x]] - sval[lson[x]]}), min({f[rson[x]], val[x] - sval[rson[x]], f[lson[x]] - sval[rson[x]]}));
	f[x] = max(min(val[x] - sval[lson[x]], f[rson[x]] - sval[lson[x]]), min(val[x] - sval[rson[x]], f[lson[x]] - sval[rson[x]]));
}
int main(){
	//freopen("fat.in","r",stdin);
	//freopen("fat.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)c[i] = read();
	for(int i = 1; i <= m; ++i){int u = read(), v = read(), w = read(); E[i] = {u, v, w};}
	sort(E + 1, E + m + 1); S.init(); cnt = n;
	for(int i = 1; i <= m; ++i)if(S.fa(E[i].u) != S.fa(E[i].v)){
		val[++cnt] = E[i].w;
		lson[cnt] = S.fa(E[i].u);
		rson[cnt] = S.fa(E[i].v);
		S.f[S.fa(E[i].u)] = cnt;
		S.f[S.fa(E[i].v)] = cnt;
	}
	dfs(cnt, 0);
	printf("%lld\n",f[cnt] > 0 ? f[cnt] : -1);
    return 0;
}

C. 分摊

暴跳父亲,发现式子为 min(size[son],val)

一个显然的东西是 val<=size

于是如果取 min 取到 val, 那么他至少扩大 2 倍,他最大为 size

所以这样的操作不会超过 log

于是倍增, mix,i 表示从从 x 向上跳到 2i 级祖先,最小为多少不会有 min()=val 的情况

svx,i 表示贡献,对于出现了的情况,可以预处理前缀和+二分快速解决

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 300005;
int n, f[maxn][20];
ll v[maxn], sv[maxn][20], mi[maxn][20];
vector<int>son[maxn];
vector<ll>sum[maxn], val[maxn];

ll sol(int x){
	ll ans = v[x];
	while(x){
		for(int i = 19; i >= 0; --i)if(x && ans >= mi[x][i]){
			ans += sv[x][i]; x = f[x][i];
		}
		if(x){
			int pos = upper_bound(val[f[x][0]].begin(), val[f[x][0]].end(), ans) - val[f[x][0]].begin() - 1;
			ll las = ans;
			if(pos < 0){ans += las * (son[f[x][0]].size() - 1);}
			else {ans += las * (son[f[x][0]].size() - pos - 2); ans += sum[f[x][0]][pos];}
			x = f[x][0];
		}
	}
	return ans;
}

int main(){
	freopen("share.in","r",stdin);
	freopen("share.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i)son[f[i][0] = read()].push_back(i), v[i] = read();
	for(int i = n; i >= 1; --i)v[f[i][0]] += v[i];
	for(int i = 1; i <= n; ++i)val[f[i][0]].push_back(v[i]), sum[f[i][0]].push_back(0);
	for(int i = 0; i <= n; ++i){
		sort(val[i].begin(), val[i].end());
		int s = val[i].size(); ll pre = 0;
		for(int j = 0; j < s; ++j)pre += val[i][j], sum[i][j] = pre; 
	}
	for(int i = 1; i <= n; ++i){
		int sf = son[f[i][0]].size();
		if(sf != 1)mi[i][0] = val[f[i][0]][sf - 1 - (val[f[i][0]][sf - 1] == v[i])];
		sv[i][0] = sum[f[i][0]][sf - 1] - v[i];
	}
	for(int j = 1; j <= 19; ++j)
		for(int i = 1; i <= n; ++i){
			f[i][j] = f[f[i][j - 1]][j - 1];
			sv[i][j] = sv[i][j - 1] + sv[f[i][j - 1]][j - 1];
			mi[i][j] = max(mi[i][j - 1], mi[f[i][j - 1]][j - 1]);
		}
	for(int i = 1; i <= n; ++i)printf("%lld\n",sol(i));
    return 0;
}

D. 修路

一个连通块无论如何连边,都能等价于顺次链接这些点构成的多边形

你发现等价完了之后新加一条边与某个连通块的交点就是 O(1) 级别,每次合并必然减少连通块,所以这里是 O(n) 级别的

考虑如何维护这个东西,记录 presuf, 维护在多边形上他的前驱后继

然后每次查询时候在线段树上找到所有有交的边即可

参考了 Administrator学长的题解

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
	int x = 0; char c = getchar();
	while(!isdigit(c))c = getchar();
	do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
	return x;
}

const int maxn = 200005;
int n, m, top, sta[maxn];
struct DSU{
	int f[maxn];
	void init(){for(int i = 1; i <= n; ++i)f[i] = i;}
	int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
	void merge(int x, int y){x = fa(x); y = fa(y); f[y] = x;}
}S;
struct seg1{
	int t[maxn << 2 | 1], pos[maxn];
	#define push_up(x) (t[x] = min(t[x << 1], t[x << 1 | 1]))
	void built(int x, int l, int r){
		if(l == r){t[x] = l; pos[l] = x; return;}
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
		push_up(x);
	}
	void modify(int p, int val){int x = pos[p]; for(t[x] = val, x >>= 1; x; x >>= 1)push_up(x);}
	void query(int x, int l, int r, int L, int R, int lim){
		if(t[x] > lim)return;
		if(l == r){sta[++top] = t[x]; sta[++top] = l; return;}
		int mid = (l + r) >> 1;
		if(L <= mid)query(x << 1, l, mid, L, R, lim);
		if(R > mid)query(x << 1 | 1, mid + 1, r, L, R, lim);
	}
	#undef push_up
}pre1;
struct seg2{
	int t[maxn << 2 | 1], pos[maxn];
	#define push_up(x) (t[x] = max(t[x << 1], t[x << 1 | 1]))
	void built(int x, int l, int r){
		if(l == r){t[x] = l; pos[l] = x; return;}
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
		push_up(x);
	}
	void modify(int p, int val){int x = pos[p]; for(t[x] = val, x >>= 1; x; x >>= 1)push_up(x);}
	void query(int x, int l, int r, int L, int R, int lim){
		if(t[x] < lim)return;
		if(l == r){sta[++top] = t[x]; sta[++top] = l; return;}
		int mid = (l + r) >> 1;
		if(L <= mid)query(x << 1, l, mid, L, R, lim);
		if(R > mid)query(x << 1 | 1, mid + 1, r, L, R, lim);
	}
	#undef push_up
}suf1;
int main(){
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
    n = read(), m = read();
	pre1.built(1, 1, n); 
	suf1.built(1, 1, n); 
	S.init();
	for(int i = 1; i <= m; ++i){
		int op = read(), u = read(), v = read();
		if(op & 1){
			top = 0;
			if(u > v)swap(u, v);
			if(S.fa(u) == S.fa(v))continue;
			pre1.query(1, 1, n, u, v, u);
			suf1.query(1, 1, n, u, v, v);
			sort(sta + 1, sta + top + 1);
			top = unique(sta + 1, sta + top + 1) - sta - 1;
			for(int j = 1; j < top; ++j)S.merge(sta[j], sta[top]);
			for(int j = 1; j < top; ++j)pre1.modify(sta[j + 1], sta[j]);
			for(int j = 2; j <= top; ++j)suf1.modify(sta[j - 1], sta[j]);
			pre1.modify(sta[top], sta[1]);
			suf1.modify(sta[1], sta[top]);
		}else printf("%d",S.fa(u) == S.fa(v));
	}
	return 0;
}

posted @   Chen_jr  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示