暑假好題彙總

目錄

[JSOI2007]文本生成器  [AC自動機 + DP]

BZOJ3687簡單題 [Bitset]

恨 7 不成妻 [數位 DP]

[SHOI2011]雙倍迴文 [Manacher]

[NOI2007]貨幣兌換 [CDQ+斜率優化DP]

BZOJ2655 calc [ 生成函數 + DP + 拉格朗日差值 ]

P3401 洛谷樹 [樹鏈剖分]

[BZOJ4205][WOJ3643]卡牌配對 [網絡流 (巧妙建圖)]

[vijos1891]學姐的逛街計劃 [網絡流]

WOJ3475 [POI2015] Pustynia [線段樹優化建圖]

我們的 CPU 遭到攻擊 [LCT]


[JSOI2007]文本生成器  [AC自動機 + DP]

我們考慮全集減補集, 也就是 26 ^ m - (一個單詞都沒有出現的次數)

對於後面可以在AC自動機上DP, f[i][j] 表示第i爲 在AC自動機上的 j 節點, 一次都沒有出現的方案數, 枚舉26個狀態轉移即可

#include<bits/stdc++.h>
#define N 6050
#define Mod 10007 
using namespace std;
int n, m, f[105][N];
int ch[N][26], fail[N], vis[N], tot;
typedef long long ll;
void insert(string s){
	int len = s.length(), now = 0;
	for(int i=0; i<len; i++){
		int x = s[i] - 'A';
		if(!ch[now][x]) ch[now][x] = ++tot;
		now = ch[now][x];
	} vis[now] = 1;
}
void Build(){
	queue<int> q;
	for(int i=0; i<26; i++) if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int i=0; i<26; i++){
			if(!ch[x][i]) ch[x][i] = ch[fail[x]][i];
			else fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]), vis[ch[x][i]]|=vis[fail[ch[x][i]]];
		}
	}
}
void add(int &x, int y){ x = (x+y+Mod) % Mod;}
ll power(ll a, ll b){
	ll ans = 1; for(;b;b>>=1){
		if(b&1) ans = (ans*a) % Mod;
		a = (a*a) % Mod;
	} return ans;
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++){
		string s; cin>>s; insert(s);
	} Build(); f[0][0] = 1;
	for(int i=0; i<=m-1; i++)
		for(int j=0; j<=tot; j++)
			for(int k=0; k<26; k++)
				if(!vis[ch[j][k]]) add(f[i+1][ch[j][k]], f[i][j]);
	int ans = power(26, m);
	for(int i=0; i<=tot; i++) add(ans, -f[m][i]);
	printf("%d", ans); return 0;
}

BZOJ3687簡單題 [Bitset]

我們記錄每一個數的出現次數 0/1, 如果是1的話就把它異或上

於是 用Bitset 類似揹包一樣處理

#include<bits/stdc++.h>
#define N 2000050
using namespace std;
int n, ans; bitset<N> S;
int main(){
	scanf("%d", &n); S[0] = 1; 
	for(int i=1; i<=n; i++){
		int x; scanf("%d", &x);
		S ^= (S<<x); 
	} 
	for(int i=1; i<=N-50; i++)
		if(S[i]) ans ^= i;
	printf("%d", ans); return 0;
} 

恨 7 不成妻 [數位 DP]

首先一個數(合法)  ?a_n a_{n-1}...a_2 a_1

從n位推到n+1位, 考慮如何算它的平方

(?a_n a_{n-1}...a_2 a_1)^2=(10^{n} * ?)^2+(10^n*?)(a_n a_{n-1}...a_2 a_1)+(a_n a_{n-1}...a_2 a_1)^2 \rightarrow \sum (?a_n a_{n-1}...a_2 a_1)^2=cnt*(10^{n} * ?)^2+(10^n*?)*sum+sum2

於是乎直接維護 cnt (合法個數), sum (合法的數的和), sum2(合法數的平方的和)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node{
	ll cnt, sum, sum2;
}f[20][8][8];
int T, a[20]; ll L, R, fmul[20];
const int Mod = 1000000007;
ll add(ll a, ll b){ return (a+b) % Mod;}
ll mul(ll a, ll b){ return (a*b) % Mod;}
Node dfs(int u, int Mod1, int Mod2, int lim){
	if(u == 0) return (Node){(Mod1 && Mod2), 0, 0};
	Node &res = f[u][Mod1][Mod2];
	if(res.cnt != -1 && !lim) return res;
	Node ans = (Node){0, 0, 0}; int up = lim ? a[u] : 9;
	for(int i=0; i<=up; i++){
		if(i == 7) continue;
		Node tmp = dfs(u-1, (Mod1 + i) % 7, (Mod2 * 10 + i) % 7, lim & (i == up));
		ll  now = mul(fmul[u], i);
		ans.cnt = add(ans.cnt, tmp.cnt);
		ans.sum = add(ans.sum, add(mul(now, tmp.cnt), tmp.sum));
		ll A = mul(mul(now, now), tmp.cnt), B = mul(2, mul(now, tmp.sum)), C = tmp.sum2;
		ans.sum2 = add(ans.sum2, add(A, add(B, C))); 
	}
	if(!lim) f[u][Mod1][Mod2] = ans;
	return ans;
} 
ll Solve(ll x){
	int cnt = 0; while(x) a[++cnt] = x % 10, x /= 10;
	return dfs(cnt, 0, 0, 1).sum2;
}
int main(){
	memset(f, -1, sizeof(f));
	fmul[1]=1; for(int i=2; i<=20; i++) fmul[i] = mul(fmul[i-1], 10);
	scanf("%d", &T);
	while(T--){
		scanf("%lld%lld", &L, &R);
		printf("%lld\n", add(Solve(R), Mod-Solve(L-1))); 
	}
}

[SHOI2011]雙倍迴文 [Manacher]

跑馬拉車的時候, 如果 i - i的半徑超過了當前的中間點id, 那麼i這邊的迴文串可以直接根據id對稱過去形成一個新的迴文串

另外, 因爲要求長度爲偶數, 所以Manacher每次 +2

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
char c[N], s[N]; int n, ans, p[N];
int main(){
	scanf("%d%s", &n, c+1); n = 0;
	int len = strlen(c+1); 
	for(int i=1; i<=len; i++){
		s[++n] = '$'; s[++n] = c[i]; 
	} s[++n] = '$';
	for(int mx=0, id=0, i=1; i<=n; i+=2){
		if(i < mx) p[i] = min(p[id * 2 - i], mx - i);
		else p[i] = 1;
		if(i < mx && i - p[i] < id) ans = max(ans, 2 * (i-id));
		while(s[i-p[i]] == s[i+p[i]]) p[i]++;
		if(i+p[i] > mx) id = i, mx = i+p[i];
	} printf("%d", ans); return 0;
}

[NOI2007]貨幣兌換 [CDQ+斜率優化DP]

首先我們的策略是, 買了發現要賺, 那麼就買完

我們設 fi 爲第i天完了過後能最多剩下的前

f_i=max(x_j*a_i+y_j*b_i)(1<=j<i, x_j=rate_j\frac{f_j}{a_j*rate_j+b_j},y_j=\frac{f_j}{a_j*rate_j+b_j})

變一下形   f_i=x_j*a_i+y_j*b_i\rightarrow y_j = -\frac{a_i}{b_i}*x_j+\frac{f_i}{b_i}

於是有了斜率優化的模型, 斜率取 ( - ai / bi ), 求能使 fi / bi 最大的點

但是 斜率不單調怎麼辦, 想到分治, 用[l -- mid]中的點 更新 [mid+1 -- r] 中的答案

插入需要按 x 排序, 查詢需要按斜率排序, 整個序列需要按時間排序

於是有了模型

void CDQ(int l, int r){
    if(l == r){ f[l] = max(f[l], f[l-1]); return;}
    int mid = (l+r) >> 1;
    Solve(l, mid)
    sort(l, mid, X);
    sort(mid+1, r, K);
    for(int i=l; i<=mid; i++) 插入凸包
    for(int i=mid+1; i<=r; i++) 更新答案
    sort(l, r, 時間)
    Solve(mid+1, r)
}

排序用歸併排序的思想可以少一個log

#include<bits/stdc++.h>
#define N 100050
using namespace std;
const double eps = 1e-9;
int n, q[N]; double f[N];
struct Node{ double a, b, rate, k, x, y; int id;} t[N], tmp[N];
bool cmp(Node a, Node b){ return a.k > b.k;}
double slope(int i, int j){ 
	if(fabs(t[i].x - t[j].x) < eps) return 1e18;
	return (t[i].y - t[j].y) / (t[i].x - t[j].x);
}
void Solve(int l, int r){
	if(l == r){
		f[l] = max(f[l], f[l-1]);
		t[l].y = f[l] / (t[l].rate * t[l].a + t[l].b);
		t[l].x = t[l].y * t[l].rate; return;
	}
	int mid = (l+r) >> 1, ql = l-1, qr = mid;
	for(int i=l; i<=r; i++) if(t[i].id <= mid) tmp[++ql] = t[i]; else tmp[++qr] = t[i];
	for(int i=l; i<=r; i++) t[i] = tmp[i];
	Solve(l, mid);
	int L = 1, R = 0;
	for(int i=l; i<=mid; i++){
		while(R > 1 && slope(q[R-1], q[R]) < slope(q[R], i) + eps) R--;
		q[++R] = i;
	}
	for(int i=mid+1; i<=r; i++){
		while(L < R && slope(q[L], q[L+1]) + eps > t[i].k) L++;
		f[t[i].id] = max(f[t[i].id], t[q[L]].x * t[i].a + t[q[L]].y * t[i].b); 
	}
	Solve(mid+1, r);
	ql = l, qr = mid+1;
	for(int i=l; i<=r; i++){
		if((t[ql].x < t[qr].x || qr > r || fabs(t[ql].x - t[qr].x) < eps) && ql <= mid)
			tmp[i] = t[ql++];
		else tmp[i] = t[qr++];
	} 
	for(int i=l; i<=r; i++) t[i] = tmp[i];
	
}
int main(){
	scanf("%d%lf", &n, &f[0]);
	for(int i=1; i<=n; i++){
		scanf("%lf%lf%lf", &t[i].a, &t[i].b, &t[i].rate);
		t[i].k =  - t[i].a / t[i].b; t[i].id = i;
	} sort(t+1, t+n+1, cmp); Solve(1, n);
	printf("%0.3lf", f[n]); return 0;
}

 


BZOJ2655 calc [ 生成函數 + DP + 拉格朗日差值 ]

先弄出答案的生成函數

\prod (1+a_1x)(1+a_2x)(1+a_3x)...(1+a_A x)

這個多項式的第 n 項就是答案

考慮DP來求, f[i][j] 表示考慮到 ai, 多項式第 j 項 的值

f(i,j)=f(i-1,j)+i * f(i-1,j-1)

看到 A 那麼大, 應該想到拉格朗日差值, 但這個東西是個多項式嗎, 繼續化簡看看

f(i,j)=f(i-1,j)+i * f(i-1,j-1) = f(i-2, j) + (i-1) * f(i-2,j-1) + i * f(i-1, j-1)=...=\sum_{k=0}^{j-1}f(k, j-1)*(k+1)

多項式就比較明顯可以看出來, 乘一個(k+1) 會多一次, 求一個前綴和會多一次, 所以答案是一個 n * 2 次的多項式

#include<bits/stdc++.h>
#define N 1050
using namespace std;
typedef long long ll;
int A, n, Mod;
ll fac[N], f[N][N], y[N];
ll add(ll a, ll b){ return ((a+b) % Mod + Mod) % Mod;}
ll mul(ll a, ll b){ return (a*b) % Mod;}
ll power(ll a, ll b){ ll ans = 1;
	for(;b;b>>=1){ if(b&1) ans = mul(ans, a); a = mul(a, a);}
	return ans;
}
ll lagrange(int k, int n){
	if(n <= k) return y[n]; 
	ll pre = 1, ans = 0;
	for(int i=1; i<=k; i++) pre = mul(pre, n-i);
	for(int i=1; i<=k; i++){
		ll inv1 = power(mul(fac[k-i], fac[i-1]), Mod-2);
		ll inv2 = power(n-i, Mod-2);
		ll flag = ((k-i) & 1) ? -1 : 1;
		ans = add(ans, flag * mul(mul(inv1, inv2), mul(pre, y[i])));
	} return ans;
}
int main(){
	scanf("%d%d%d", &A, &n, &Mod);
	fac[0] = fac[1] = 1;
	for(int i=2; i<=N-50; i++) fac[i] = mul(fac[i-1], i);
	f[0][0] = 1;
	for(int i=1; i<=2*n+1; i++){
		f[i][0] = 1;
		for(int j=1; j<=n; j++){
			f[i][j] = add(f[i-1][j], mul(f[i-1][j-1], i));
		}
	}
	for(int i=1; i<=n*2+1; i++) y[i] = f[i][n];
	ll ans = lagrange(n*2+1, A);
	printf("%lld", mul(ans, fac[n]));
	return 0;
}

P3401 洛谷樹 [樹鏈剖分]

首先可以想到, 求出每個點到根的異或和, 然後枚舉兩個點算貢獻就可以了

然後發現每個數 < 1023, 明擺着讓你拆成每一位考慮, 然後估價就是線段樹維護0/1序列什麼的

所以現在可以先dfs一遍求出異或和, 然後對異或和按位考慮

哪些點對會產生貢獻 ? 只有一個0, 一個1, 那麼樹剖求出0, 1 的數量, 乘一下就可以了

修改一條邊只會影響子樹的異或和, 線段樹區間修改(0/1翻轉) 就可以了

#include<bits/stdc++.h>
#define N 60050
using namespace std;
int first[N], nxt[N], to[N], w[N], tot;
void add(int x, int y, int z){
	nxt[++tot] = first[x], first[x] = tot;
	to[tot] = y, w[tot] = z;
}
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, m, a[N], val[N];
int siz[N], son[N], fa[N], dep[N], sum[N];
int top[N], id[N], pre[N], sign;
void dfs1(int u, int f){
	siz[u] = 1;
	for(int i=first[u];i;i=nxt[i]){
		int t = to[i]; if(t == f) continue;
		fa[t] = u; dep[t] = dep[u] + 1; sum[t] = sum[u] ^ w[i];
		val[t] = w[i];
		dfs1(t, u); siz[u] += siz[t];
		if(siz[t] > siz[son[u]]) son[u] = t;
	}
}
void dfs2(int u, int Top){
	id[u] = ++sign; pre[sign] = u; top[u] = Top;
	if(son[u]) dfs2(son[u], Top);
	for(int i=first[u];i;i=nxt[i]){
		int t = to[i]; if(t == fa[u] || t == son[u]) continue;
		dfs2(t, t);
	}
}
struct Ak{
	int sum[N<<2], tag[N<<2];
	void Pushup(int x){ sum[x] = sum[x<<1] + sum[x<<1|1]; }
	void Build(int x, int l, int r){
		if(l == r){ sum[x] = a[pre[l]]; return;}
		int mid = (l+r) >> 1; Build(x<<1, l, mid); Build(x<<1|1, mid+1, r);
		Pushup(x);
	}
	void Pushtag(int x, int l, int r){
		tag[x] ^= 1; sum[x] = r - l + 1 - sum[x];
	}
	void Pushdown(int x, int l, int r){
		if(tag[x]){
			int mid = (l+r) >> 1;
			Pushtag(x<<1, l, mid); Pushtag(x<<1|1, mid+1, r);
			tag[x] = 0;
		}
	}
	void Modify(int x, int l, int r, int L, int R){
		if(L<=l && r<=R){ Pushtag(x, l, r); return;}
		Pushdown(x, l, r); int mid = (l+r) >> 1;
		if(L<=mid) Modify(x<<1, l, mid, L, R);
		if(R>mid) Modify(x<<1|1, mid+1, r, L, R);
		Pushup(x);
	}
	int Qu(int x, int l, int r, int L, int R){
		if(L<=l && r<=R) return sum[x];
		Pushdown(x, l, r); int mid = (l+r) >> 1, ans = 0;
		if(L<=mid) ans += Qu(x<<1, l, mid, L, R);
		if(R>mid) ans += Qu(x<<1|1, mid+1, r, L, R);
		return ans;
	}
	int Quary(int u, int v){
		int sum1 = 0, x = u, y = v;
		while(top[u] != top[v]){
			if(dep[top[u]] < dep[top[v]]) swap(u, v);
			sum1 += Qu(1, 1, n, id[top[u]], id[u]);
			u = fa[top[u]];
		} if(id[u] > id[v]) swap(u, v);
		sum1 += Qu(1, 1, n, id[u], id[v]); //  lca -> u
		int sum = dep[x] + dep[y] - dep[u] * 2 + 1;
		return (sum - sum1) * sum1;
	}
}A[10];
int main(){
	n = read(), m = read();
	for(int i=1; i<n; i++){
		int x = read(), y = read(), z = read();
		add(x, y, z); add(y, x, z);
	} dep[1] = 1; dfs1(1, 0); dfs2(1, 1);
	for(int k=0; k<=9; k++){
		for(int i=1; i<=n; i++) a[i] = (sum[i] >> k) & 1;
		A[k].Build(1, 1, n);
	}
	while(m--){
		int op = read(), u = read(), v = read();
		if(op == 1){ 
			long long ans = 0;
			for(int k=0; k<=9; k++) ans += 1ll * (1<<k) * A[k].Quary(u, v);
			printf("%lld\n", ans);
		}
		if(op == 2){
			int w = read(); if(fa[u] != v) swap(u, v); 
			for(int k=0; k<=9; k++){
				if(((w ^ val[u]) >> k) & 1) A[k].Modify(1, 1, n, id[u], id[u] + siz[u] - 1);
			} val[u] = w;
		} 
	} return 0; 
}

[BZOJ4205][WOJ3643]卡牌配對 [網絡流 (巧妙建圖)]

首先 可以暴力 n^2 匹配 ... 然後一籌莫展

好, 先不考慮3個, 考慮1個, 我們加一列點分別代表(2, 3, 5, 7, 11...)(共46個), 然後左邊這一列向它的質因子連邊

例如 30 向 2, 3, 5 連邊, 右邊也同理, 邊權爲inf, 然後跑最大流

這樣做顯然是正確的, 因爲左邊要到右邊, 必須經過中間的點, 也就是說不互質

再考慮兩個, 嗯, 我們建 46 * 46個點不就完了, 比如說 (2, 3)這個點表示A類爲2的倍數, B類爲3的倍數

這樣做就做到了對邊分類一起建, 而不是n ^ 2 一個一個連邊

在來3個, 題目要求至少兩個不互質, 所以要麼(A,B), 要麼(B, C), 要麼(A,C)

於是我們建 3 * 46 * 46 個虛點表示上述情況, 此題完

#include<bits/stdc++.h>
#define N 5000050
using namespace std;
int first[N], nxt[N], to[N], w[N], tot = 1;
void add(int x, int y, int z){
	nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;
	nxt[++tot] = first[y], first[y] = tot, to[tot] = x, w[tot] = 0;
}
const int inf = 0x3fffffff;
int Map[3][50][50];
int prim[50] = {
    1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 
    43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 
    103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 
    163, 167, 173, 179, 181, 191, 193, 197, 199
};
vector<int> v[205];
int n, m, st, ed, dis[N];
bool bfs(){
	queue<int> q; q.push(st);
	memset(dis, -1, sizeof(dis)); dis[st] = 0;
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int i=first[x];i;i=nxt[i]){
			int t = to[i]; if(dis[t] == -1 && w[i]){ 
				dis[t] = dis[x] + 1; q.push(t);
				if(t == ed) return true;
			}
		}
	} return false;
}
int dfs(int u, int flow){
	if(u == ed) return flow;
	int ans = 0;
	for(int i=first[u];i;i=nxt[i]){
		int t = to[i]; if(dis[t] == dis[u] + 1){
			int delta = dfs(t, min(flow, w[i]));
			w[i] -= delta; w[i^1] += delta;
			ans += delta; flow -= delta;
			if(!flow) break;
		}
	} if(flow) dis[u] = -1;
	return ans;
}
int dinic(){ int ans = 0; while(bfs()) ans += dfs(st, inf); return ans;}
int main(){
	scanf("%d%d", &n, &m);
	for(int i=2; i<=200; i++)
		for(int j=1; j<=46; j++)
			if(i % prim[j] == 0) v[i].push_back(j);
	for(int i=0, c=m+n; i<3; i++)
		for(int j=1; j<=46; j++)
			for(int k=1; k<=46; k++)
				Map[i][j][k] = ++c;
	st = 0, ed = n+m+46*46*3+1;
	for(int i=1; i<=n; i++){
		add(st, i, 1);
		int a, b, c; scanf("%d%d%d", &a, &b, &c);
		for(int j=0; j<v[a].size(); j++)
			for(int k=0; k<v[b].size(); k++)
				add(i, Map[0][v[a][j]][v[b][k]], inf);
		for(int j=0; j<v[b].size(); j++)
			for(int k=0; k<v[c].size(); k++)
				add(i, Map[1][v[b][j]][v[c][k]], inf);
		for(int j=0; j<v[c].size(); j++)
			for(int k=0; k<v[a].size(); k++)
				add(i, Map[2][v[c][j]][v[a][k]], inf);
	}
	for(int i=1; i<=m; i++){
		add(i+n, ed, 1);
		int a, b, c; scanf("%d%d%d", &a, &b, &c);
		for(int j=0; j<v[a].size(); j++)
			for(int k=0; k<v[b].size(); k++)
				add(Map[0][v[a][j]][v[b][k]], i+n, inf);
		for(int j=0; j<v[b].size(); j++)
			for(int k=0; k<v[c].size(); k++)
				add(Map[1][v[b][j]][v[c][k]], i+n, inf);
		for(int j=0; j<v[c].size(); j++)
			for(int k=0; k<v[a].size(); k++)
				add(Map[2][v[c][j]][v[a][k]], i+n, inf);
	} printf("%d", dinic());
	return 0;
}

[vijos1891]學姐的逛街計劃 [網絡流]

首先原點給1分配k的流量, 如果1號點不選, 全部傳給2, 於是1向2連費用爲0, 流量爲inf的邊

如果1選了, 1用掉的流量要6才能用, 與1向6連費用爲w1, 流量爲1的邊, 然後就是最大費用最大流

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int first[N], nxt[N], to[N], w[N], c[N], tot = 1;
void add(int x, int y, int z, int v){
	nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z; c[tot] = v;
	nxt[++tot] = first[y], first[y] = tot, to[tot] = x, w[tot] = 0; c[tot] = -v;
} 
int n, k, a[N], st, ed;
int dis[N], vis[N], from[N], froms[N];
bool spfa(){
	queue<int> q; q.push(st); 
	memset(dis, 63, sizeof(dis)); dis[st] = 0; vis[st] = 1;
	int Inf = dis[1];
	while(!q.empty()){
		int x = q.front(); vis[x] = 0; q.pop();
		for(int i=first[x];i;i=nxt[i]){
			int t = to[i]; if(w[i] && dis[t] > dis[x] + c[i]){
				dis[t] = dis[x] + c[i]; from[t] = x; froms[t] = i;
				if(!vis[t]) q.push(t), vis[t] = 1;
			}
		}
	} return dis[ed] != Inf;
}
int calc(){
	int now = ed, flow = 1e9;
	while(now != st){
		flow = min(flow, w[froms[now]]);
		now = from[now];
	} now = ed;
	while(now != st){
		w[froms[now]] -= flow;
		w[froms[now] ^ 1] += flow;
		now = from[now];
	} return flow;
}
int dinic(){
	int ans = 0; while(spfa()) ans += calc() * dis[ed]; return ans;
}
int main(){
	scanf("%d%d", &n, &k); st = 0, ed = n*3+1;
	for(int i=1; i<=n*3; i++) scanf("%d", &a[i]);
	for(int i=1; i<=n*3-1; i++) add(i, i+1, k, 0);
	for(int i=1; i<=n*2; i++) add(i, i+n, 1, -a[i]);
	for(int i=n*2+1; i<=n*3; i++) add(i, ed, 1, -a[i]);
	add(st, 1, k, 0); add(n*3, ed, k, 0);
	printf("%d", -dinic()); return 0;
}

WOJ3475 [POI2015] Pustynia [線段樹優化建圖]

我開始想, 這k個點每個都向logn個線段樹的點連邊不會炸嗎, 後來才發現可以建虛點

虛點 向 當前x連邊(權值爲0), 線段樹上的點向虛點連邊(權值爲1), 然後拓撲排序就可以了

// 虛點 + 線段樹優化建圖 
#include<bits/stdc++.h>
#define N 2000050
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 first[N], nxt[N], to[N], w[N], tot, du[N];
void add(int x, int y, int z){
	nxt[++tot] = first[x], first[x] = tot;
	to[tot] = y, w[tot] = z; du[y]++;
}
int n, s, m, cnt, a[N], f[N];
int ls[N], rs[N], rt;
void Build(int &x, int l, int r){
	if(l == r){ x = l; return;} x = ++cnt;
	int mid = (l+r) >> 1; Build(ls[x], l, mid); Build(rs[x], mid+1, r);
	add(ls[x], x, 0); add(rs[x], x, 0);
}
void Modify(int x, int l, int r, int L, int R, int u, int v){
	if(L>R) return;
	if(L<=l && r<=R){ add(x, u, v); return;}
	int mid = (l+r) >> 1;
	if(L<=mid) Modify(ls[x], l, mid, L, R, u, v);
	if(R>mid) Modify(rs[x], mid+1, r, L, R, u, v);
}
void topsort(){
	queue<int> q; int res = 0;
	for(int i=1; i<=cnt; i++) if(!du[i]) q.push(i);
	while(!q.empty()){
		int x = q.front(); q.pop(); res++;
		if(f[x] > 1e9){ printf("NIE"); exit(0);}
		if(f[x] > a[x] && a[x]){ printf("NIE"); exit(0);}
		f[x] = max(f[x], a[x]);
		for(int i=first[x];i;i=nxt[i]){
			int t = to[i]; du[t]--;
			if(!du[t]) q.push(t);
			f[t] = max(f[t], f[x] + w[i]);
		}
	} if(res < cnt){ printf("NIE"); exit(0);}
}
int main(){
	n = cnt = read(), s = read(), m = read(); 
	for(int i=1; i<=s; i++){
		int p = read(), v = read(); a[p] = v;
	} Build(rt, 1, n);
	for(int i=1; i<=m; i++){
		int l = read(), r = read(), k = read(); cnt++;
		for(int i=1; i<=k; i++){
			int x = read();
			add(cnt, x, 0);
			Modify(rt, 1, n, l, x-1, cnt, 1); l = x+1;
		} if(l <= r) Modify(rt, 1, n, l, r, cnt, 1);
	} topsort();
	printf("TAK\n");
	for(int i=1; i<=n; i++) printf("%d ", f[i]);
	return 0;
}

我們的 CPU 遭到攻擊 [LCT]

LCT 不支持連邊, 只能將邊轉成點表示, 做了QTREE發現此類LCT有一個套路 ---- 維護到當前Splay最淺的點的答案, 與最深的點的答案, 以下用sumL, sumR 表示

這是Splay維護的一條鏈

 所有黑點到1的和 sumL(4) = sumL(ls(4)) + siz(rs(4)) * 邊權和(ls(4)) + siz(虛子樹) * 邊權和(ls(4))

sumR 同理, 然後就是LCT維護子樹操作了, 一堆細節, reverse的時候記得翻轉sumL, sumR

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
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, m, k, tot;
struct Node{
	int ch[2], fa, siz, Siz, col;
	ll val, sumE, Sum, sumL, sumR;
	bool rev; 
}t[N];
#define ls t[x].ch[0]
#define rs t[x].ch[1]
bool isRoot(int x){ return t[t[x].fa].ch[0] != x && t[t[x].fa].ch[1] != x;}
void Pushup(int x){
	t[x].siz = t[ls].siz + t[rs].siz + t[x].col + t[x].Siz;
	t[x].sumE = t[ls].sumE + t[rs].sumE + t[x].val;
	t[x].sumL = t[ls].sumL + t[rs].sumL + t[x].Sum + (t[ls].sumE + t[x].val) * (t[rs].siz + t[x].Siz + t[x].col);
 	t[x].sumR = t[ls].sumR + t[rs].sumR + t[x].Sum + (t[rs].sumE + t[x].val) * (t[ls].siz + t[x].Siz + t[x].col);
}
void Pushrev(int x){ if(!x) return; swap(ls, rs); swap(t[x].sumL, t[x].sumR); t[x].rev ^= 1;}
void Pushdown(int x){
	if(t[x].rev) Pushrev(ls), Pushrev(rs), t[x].rev = 0;
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa, k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x; t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1]; t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x; Pushup(y); Pushup(x); 
}
void Pushpath(int x){if(!isRoot(x)) Pushpath(t[x].fa); Pushdown(x);}
void Splay(int x){
	Pushpath(x); while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y)) rotate((t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? x : y);
		rotate(x);
	} Pushup(x);
} 
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa){
		Splay(x); 
		if(rs) t[x].Siz += t[rs].siz, t[x].Sum += t[rs].sumL;
		rs = y;
		if(rs) t[x].Siz -= t[rs].siz, t[x].Sum -= t[rs].sumL;
		Pushup(x);
	}
}
void Makeroot(int x){ Access(x); Splay(x); Pushrev(x);}
void Split(int x, int y){ Makeroot(x); Access(y); Splay(y);}
void Link(int u, int v){
	Makeroot(u); Makeroot(v);
	t[v].Siz += t[u].siz;
	t[v].Sum += t[u].sumL;
	t[u].fa = v; Pushup(v); 
}
void link(int u, int v, int w){
	t[++tot].val = w; Link(u, tot); Link(tot, v);
}
void Cut(int u, int v){
	Split(u, v); 
	if(t[u].fa == v){ t[u].ch[1] = 0; Pushup(u);}
	else t[u].fa = t[t[u].fa].ch[0] = 0;
	t[v].ch[0] = t[t[v].ch[0]].fa = 0;
	Pushup(v);
}
ll Quary(int x){ Makeroot(x); return t[x].sumL;} 
int main(){
	n = tot = read(), m = read(), k = read();
	for(int i=1; i<=m; i++){
		int u = read(), v = read(), w = read();
		link(u, v, w);
	}
	while(k--){
		char op[3]; scanf("%s", op);
		if(op[0] == 'L'){
			int u = read(), v = read(), w = read();
			link(u, v, w);
		}
		if(op[0] == 'C'){
			int u = read(), v = read(); Cut(u, v);
		}
		if(op[0] == 'F'){
			int x = read(); Makeroot(x); t[x].col ^= 1; Pushup(x);
		}
		if(op[0] == 'Q'){
			int x = read(); printf("%lld\n", Quary(x));
		}
	} return 0; 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章