省选模拟 19/10/05

T1:CF19E Fairy
首先暴力的线段树分治可以拿 70 p t s 70pts 70pts
有一个遗憾就是考场想了一半正解然后发现线段树分治可以拿不错的分数就苟且偷生了,看来以后做题有时间的话还是要追求卓越
考场上的思路就是先 d f s dfs dfs出一棵树,然后发现不能有奇环
然后我发现合法的边必须出现在所有奇环中,然后傻逼地打了个树剖 (差分就可以完事)
然后开始对拍,发现这么做总比 s t d std std跑出来多,然后我发现一个环可能还含有两条非树边
c n m cnm cnm 这可做吗?然后就滚去打了个线段树分治
考虑多条非树边对答案的影响:
多条太智障了,先考虑两条
1.它们不相交,可以去死了
2.它们相交
讨论:两条奇环:还有搞,一奇一偶:删去这条边后,这两个环会连成一个奇环
于是乎合法的边的条件是:
1.被所有奇环覆盖
2.不被任何一个偶环覆盖
然后差分即可
还有一个性质就是无向图 d f s dfs dfs 出来的一棵树只会有反祖边,不会有横插边
因为如果插到了两棵树上从第一棵就会直接到第二棵了

#include<bits/stdc++.h>
#define N 2000005
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt * 10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Edge{ int u, v, od, ev, flg; } e[N];
int first[N], nxt[N << 1], to[N << 1], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
int n, m, dep[N], cnt, ev[N], od[N]; bool vis[N];
void dfs(int u){
	vis[u] = 1;
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(vis[t]) continue;
		dep[t] = dep[u] + 1; e[(i + 1) >> 1].flg = 1; dfs(t);
	}
}
void dfs2(int u){
	vis[u] = 1;
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(!e[(i + 1) >> 1].flg || vis[t]) continue;
		dfs2(t); ev[u] += ev[t]; od[u] += od[t];
		e[(i + 1) >> 1].od = od[t]; e[(i + 1) >> 1].ev = ev[t];
	}  
}
vector<int> ans;
int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; i++){
		int x = read(), y = read();
		add(x, y); add(y, x);
		e[i] = (Edge){x, y, 0, 0, 0};
	} 
	for(int i = 1; i <= n; i++) if(!vis[i]) dfs(i);
	for(int i = 1; i <= m; i++){
		if(e[i].flg) continue;
		int u = e[i].u, v = e[i].v;
		if(dep[u] > dep[v]) swap(u, v);
		if((dep[v] - dep[u]) & 1) ev[u]--, ev[v]++;
		else od[u]--, od[v]++, cnt++, e[i].od = 1;
	}
	if(cnt == 0){
		cout << m << '\n';
		for(int i = 1; i <= m; i++) cout << i << " ";
		return 0;
	}
	memset(vis, 0, sizeof(vis));
	for(int i = 1; i <= n; i++) if(!vis[i]) dfs2(i);
	for(int i = 1; i <= m; i++){
		if(e[i].flg){
			if(e[i].od == cnt && e[i].ev == 0) ans.push_back(i);
		} else if(cnt == 1 && e[i].od) ans.push_back(i);
	}
	cout << ans.size() << '\n';
	for(int i = 0; i < ans.size(); i++) cout << ans[i] << " ";
	return 0;
}

T2:BZOJ 3682
给你一个字符串 S S S,初始长度为 l e n len len,还有一个 n n n 个元素的序列 P P P
接下来 m m m 个操作,有三种类型,分别是:
1 在字符串前面加入一个字符
2. 修改 P P P 中一个元素的值
3. 询问对于所有 i ∈ [ l , r ] i∈[l,r] i[l,r],使得 S [ L − P [ i ] + 1.. L ] S[L-P[i]+1..L] S[LP[i]+1..L]字典序最小的 i i i
强制在线

发现需要在线获得一个数的排名
法 1:
考虑直接用线段树维护最小的 i,难点就在 p u s h u p pushup pushup
发现每次 p u s h u p pushup pushup 都要比两个的大小,于是二分 + hash 就有 10 p t s 10pts 10pts O ( n ∗ l o g 2 n ) O(n*log^2n) O(nlog2n)算法
法 2:
在前面加,好像就是个后缀平衡树的板子
关于后缀平衡树:
支持,往前插,从前面删,得到某个后缀的 r k rk rk, S A SA SA,比较后缀大小
怎么维护呢,简单,跟平衡树一样,插进去的时候跟当前比,比用二分 + hash,于是我们又回到了 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n) 的好算法
考虑到当前点之前的后缀都被插过了,于是比的时候先比第一个字符
如果一样就比后面,而后面的已经知道
然后给每个点分配一个很大很大的权值,比大小就可以 O ( 1 ) O(1) O(1)
于是就可以 O ( n l o g n ) O(nlogn) O(nlogn) 做了
平衡树维护的时候如果 r o t a t e rotate rotate 了就暴力重建子树分配的权值,期望次数不会很多

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt * 10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int Rnd(){ return rand() | (rand() << 15);}
int n, m, len, pos[N], ans, rt;
char S[N];
typedef long long ll;

int ch[N][2], rnd[N], val[N], node; ll key[N];

bool cmp(int x, int y){ return val[x] < val[y] || val[x] == val[y] && key[x-1] < key[y-1];}
void pia(int x, ll l, ll r){
	if(!x) return; ll mid = (l+r) >> 1;
	key[x] = mid; pia(ch[x][0], l, mid - 1); pia(ch[x][1], mid + 1, r);
}
void rot(int x, int &y, ll l, ll r){
	int k = ch[y][1] == x; ch[y][k] = ch[x][k^1];
	ch[x][k^1] = y; y = x; pia(y, l, r);
}
void ins(int &x, int p, ll l, ll r){
	if(!x){ x = node; key[node] = (l + r) >> 1; return; }
	ll mid = (l+r) >> 1;
	if(cmp(p, x)){
		ins(ch[x][0], p, l, mid - 1);
		if(rnd[x] > rnd[ch[x][0]]) rot(ch[x][0], x, l, r);
	}
	else{
		ins(ch[x][1], p, mid + 1, r);
		if(rnd[x] > rnd[ch[x][1]]) rot(ch[x][1], x, l, r);
	}
}
struct Segmentree{
	int v[N << 2];
	int merge(int a, int b){ return key[pos[a]] <= key[pos[b]] ? a : b;}
	void pushup(int x){ v[x] = merge(v[x<<1], v[x<<1|1]); }
	void build(int x, int l, int r){
		if(l == r){ v[x] = l; return; }
		int mid = (l+r) >> 1;
		build(x<<1, l, mid); build(x<<1|1, mid+1, r);
		pushup(x);
	}
	void modify(int x, int l, int r, int p, int val){
		if(l == r){ pos[p] = val; return;} int mid = (l+r) >> 1;
		if(p <= mid) modify(x<<1, l, mid, p, val);
		else modify(x<<1|1, mid+1, r, p, val);
		pushup(x);
	}
	int query(int x, int l, int r, int L, int R){
		if(L<=l && r<=R) return v[x]; int mid = (l+r) >> 1;
		if(L>mid) return query(x<<1|1, mid+1, r, L, R);
		else if(R<=mid) return query(x<<1, l, mid, L, R);
		else return merge(query(x<<1, l, mid, L, R), query(x<<1|1, mid+1, r, L, R));
	}
}Seg;
int main(){
	n = read(), m = read(); len = read();
	scanf("%s", S + 1);
	for(int i = 1; i <= len; i++){
		val[++node] = S[len - i + 1] - 'a' + 1;
		rnd[node] = Rnd();
		ins(rt, i, 1, 1ll << 61);
	}
	for(int i = 1; i <= n; i++) pos[i] = read();
	Seg.build(1, 1, n);
	while(m--){
		char op[3]; scanf("%s", op);
		if(op[0] == 'I'){
			int x = (read() ^ ans) + 1;
			val[++node] = x; rnd[node] = Rnd();
			ins(rt, node, 1, 1ll << 61);
		}
		if(op[0] == 'C'){ int x = read(), y = read(); Seg.modify(1, 1, n, x, y); }
		if(op[0] == 'Q'){ int l = read(), r = read(); ans = Seg.query(1, 1, n, l, r); cout << ans << '\n';}
	} return 0;
}

CF768G The Winds of Winter
删除一个点后肯定把最大的分配给最小的
于是二分一个 m i d mid mid,最少分 m a x − m i d max-mid maxmid,最多分 m i d − m i n mid-min midmin
于是就成了查询子树有没有在 [ m a x − m i d , m i d − m i n ] [max-mid,mid-min] [maxmid,midmin] 区间的 s i z siz siz
直接主席树
等等,最大在父亲怎么办,发现有影响的只有这一条链
链上所有的点的 s i z siz siz 会减去 s i z n o w siz_{now} siznow
于是可以维护一个链的主席树,先减去 [ m a x − m i d , m i d − m i n ] [max-mid,mid-min] [maxmid,midmin]的贡献,再加上 [ m a x − m i d + s i z n o w , m i d − m i n + s i z n o w ] [max-mid+siz_{now},mid-min+siz_{now}] [maxmid+siznow,midmin+siznow] 的贡献就可以了

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt * 10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, root;
int first[N], nxt[N], to[N], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
int siz[N], st[N], ed[N], sign, pos[N], ans[N];
struct Presidentree{
	struct Node{ int ls, rs, val; } t[N * 20];
	int rt[N], node;
	void build(int &x, int l, int r){
		x = ++node; if(l == r) return; int mid = (l+r) >> 1;
		build(t[x].ls, l, mid); build(t[x].rs, mid+1, r);
	}
	void ins(int &x, int las, int l, int r, int p){
		x = ++node; t[x] = t[las]; ++t[x].val; if(l == r) return; int mid = (l+r) >> 1;
		if(p <= mid) ins(t[x].ls, t[las].ls, l, mid, p); else ins(t[x].rs, t[las].rs, mid+1, r, p);
	}
	int query(int a, int b, int l, int r, int L, int R){
		if(!a) return 0;
		if(L<=l && r<=R) return t[a].val - t[b].val;
		int mid = (l+r) >> 1, ans = 0;
		if(L<=mid) ans += query(t[a].ls, t[b].ls, l, mid, L, R);
		if(R>mid) ans += query(t[a].rs, t[b].rs, mid+1, r, L, R);
		return ans;
	}
}T[2];
void dfs(int u){
	siz[u] = 1; st[u] = ++sign; pos[sign] = u;
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; dfs(t); siz[u] += siz[t];
	} ed[u] = sign;
}
void get(int u, int fa){
	T[1].ins(T[1].rt[u], T[1].rt[fa], 1, n, siz[u]);
	for(int i = first[u]; i; i = nxt[i]) get(to[i], u);
}
int id[N];
void calc(int u, int fa){
	vector<int> tp;
	for(int i = first[u]; i; i = nxt[i]) tp.push_back(siz[to[i]]), id[siz[to[i]]] = to[i];
	if(n - siz[u]) tp.push_back(n - siz[u]), id[n - siz[u]] = fa;
	sort(tp.begin(), tp.end());
	int num = tp.size();
	if(num == 1){ ans[u] = tp[0]; }
	if(tp[num - 1] == tp[num - 2]){ ans[u] = tp[num - 1];}
	if(!ans[u]){
		int l = max(tp[num - 2], (tp[0] + tp[num - 1] + 1) / 2), r = tp[num - 1];
		int now = id[tp[num - 1]];
		while(l < r){
			int mid = (l+r) >> 1;
			if(now == fa){
				int sum = T[0].query(T[0].rt[ed[root]], T[0].rt[st[root]-1], 1, n, tp[num - 1] - mid, mid - tp[0]);
				sum -= T[0].query(T[0].rt[ed[u]], T[0].rt[st[u]-1], 1, n, tp[num - 1] - mid, mid - tp[0]);
				sum -= T[1].query(T[1].rt[fa], T[1].rt[0], 1, n, tp[num - 1] - mid, mid - tp[0]);
				sum += T[1].query(T[1].rt[fa], T[1].rt[0], 1, n, tp[num - 1] - mid + siz[u], mid - tp[0] + siz[u]);
				if(sum > 0) r = mid;
				else l = mid + 1;
			}
			else{
				if(T[0].query(T[0].rt[ed[now]], T[0].rt[st[now]-1], 1, n, tp[num - 1] - mid, mid - tp[0])) r = mid;
				else l = mid + 1; 
			}
		} ans[u] = l;
	}
	for(int i = first[u]; i; i = nxt[i]) calc(to[i], u);
}
int main(){
	freopen("winter.in","r",stdin);
	freopen("winter.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; i++){
		int x = read(), y = read(); if(!x) root = y;
		else add(x, y);
	} 
	T[1].build(T[1].rt[0], 1, n);
	dfs(root); get(root, 0);
	T[0].build(T[0].rt[0], 1, n);
	for(int i = 1; i <= n; i++) T[0].ins(T[0].rt[i], T[0].rt[i-1], 1, n, siz[pos[i]]);
	calc(root, 0);
	for(int i = 1; i <= n; i++) cout << ans[i] << '\n';
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值