20251011 总结

P9870 [NOIP2023] 双序列拓展

首先有一个暴力做法。

翻译一下题意,即为:

图片

拓展也就是一个DP,我们设朴素DP,每次判断能否往下拓展,设fij表示x匹配到第i位,y匹配到第j位。

然后考虑特殊性质:

图片

也就是说,这个特殊性质就是两个框往左上扫,扫到左上即可,

图片

然后考虑正解,X最小值和Y最大值的位置不再固定,但是发现左上和右下可以独立考虑

图片

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

const int N = 5e5 + 5;

int f[N], g[N];

struct Node {
	int min, max;
	Node(int ge = 0, int fe = 0) : min(ge), max(fe) {}
};

Node preX[N], preY[N], sufX[N], sufY[N];

inline Node update_node(int T[], const Node &p, int i) {
	return Node(T[i] < T[p.min] ? i : p.min, T[i] > T[p.max] ? i : p.max);
}

bool check1(int x, int y, int n, int m) {
	if (x == 1 || y == 1) return true;
	Node X = preX[x - 1], Y = preY[y - 1];
	if (f[X.min] < g[Y.min]) return check1(X.min, y, n, m);
	if (g[Y.max] > f[X.max]) return check1(x, Y.max, n, m);
	return false;
}

bool check2(int x, int y, int n, int m) {
	if (x == n || y == m) return true;
	Node X = sufX[x + 1], Y = sufY[y + 1];
	if (f[X.min] < g[Y.min]) return check2(X.min, y, n, m);
	if (g[Y.max] > f[X.max]) return check2(x, Y.max, n, m);
	return false;
}

bool solve(int tmpf[], int tmpg[], int n, int m) {
	if (tmpf[1] >= tmpg[1]) return false;
	for (int i = 1; i <= n; i++) f[i] = tmpf[i];
	for (int i = 1; i <= m; i++) g[i] = tmpg[i];
	for (int i = 1; i <= n; i++)
		preX[i] = (i == 1) ? Node(1, 1) : update_node(f, preX[i - 1], i);
	for (int i = 1; i <= m; i++)
		preY[i] = (i == 1) ? Node(1, 1) : update_node(g, preY[i - 1], i);
	for (int i = n; i >= 1; i--)
		sufX[i] = (i == n) ? Node(n, n) : update_node(f, sufX[i + 1], i);
	for (int i = m; i >= 1; i--)
		sufY[i] = (i == m) ? Node(m, m) : update_node(g, sufY[i + 1], i);
	Node X = preX[n], Y = preY[m];
	if (f[X.min] >= g[Y.min] || g[Y.max] <= f[X.max]) return false;
	return check1(X.min, Y.max, n, m) && check2(X.min, Y.max, n, m);
}

int tx[N], ty[N], ttx[N], tty[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int dummy, n, m, q;
	cin >> dummy >> n >> m >> q;
	for (int i = 1; i <= n; i++) cin >> tx[i];
	for (int i = 1; i <= m; i++) cin >> ty[i];
	cout << (solve(tx, ty, n, m) || solve(ty, tx, m, n) ? '1' : '0');
	while (q--) {
		for (int i = 1; i <= n; i++) ttx[i] = tx[i];
		for (int i = 1; i <= m; i++) tty[i] = ty[i];
		int cx, cy;
		cin >> cx >> cy;
		while (cx--) {
			int p, v;
			cin >> p >> v;
			ttx[p] = v;
		}
		while (cy--) {
			int p, v;
			cin >> p >> v;
			tty[p] = v;
		}
		cout << (solve(ttx, tty, n, m) || solve(tty, ttx, m, n) ? '1' : '0');
	}
	return 0;
}


P6817 [PA 2013] Filary

图片

基础模拟赛有类似的题。

首先有一个性质,发现m=2时k是一定大于n/2的,所以答案下界是这个

所以任意一个数,在答案内的概率大于1/2

所以就随机选一个,其他数与他差分,我们要选尽可能多的数使它们的GCD大于1

直接枚举GCD不行。

图片

这里写错了,是预处理最大质因子即可。

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

constexpr int maxn = 1e5+10, maxm = 1e7+10;
int n;
int a[maxn], b[maxn], pri[maxm], tot;
int lpf[maxm];
bool vis[maxm];
mt19937 rnd(114514);

void getprime(int n){
	for(int i = 2;i <= n;i++){
		if(!vis[i]) pri[++tot] = i;
		for(int j = 1;j <= tot && i * pri[j] <= n;j++){
			vis[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
		}
	}
	for(int i = 1;i <= tot;i++){
		for(int j = pri[i];j <= n;j += pri[i]) lpf[j] = pri[i];
	}
}

pair<int,int> slove(){
	int pos = rnd() % n + 1;
	map<int,int> mp;
	for(int i = 1;i <= n;i++){
		b[i] = abs(a[i] - a[pos]);
		int c = b[i];
		while(c > 1){
			int d = lpf[c];
			mp[d]++;
			while(c % d == 0) c /= d;
		}
	}
	
	int mx = 0, ans = 0;
	for(auto [x, y] : mp) mx = max(mx,y);
	for(auto [x,y] : mp){
		if(y == mx){
			int res = 0;
			for(int i = 1;i <= n;i++){
				if(b[i] % x == 0) res = gcd(res,b[i]);
			}
			ans = max(ans,res);
		}
	}
	return{mx + count(b+1,b+n+1,0),ans};
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i = 1;i <= n;i++) cin>>a[i];
	getprime(*max_element(a + 1, a + n + 1));
	pair<int,int> res(0, 0);
	while((double)clock() / CLOCKS_PER_SEC < 1.3) res = max(res, slove());
	cout << res.first << ' ' << res.second << '\n';
	return 0;
}

P4757 [CERC2014] Parades

图片

转移很奇怪的树形DP

首先设f[i]表示以i为根的子树的最优解。

根据这一类套路,肯定是DP过程中保存是否往父亲放边的。

首先有一个结论,就是如果根是关键点,根往儿子连边(牺牲一条u-son)一定不劣于牺牲两条u-son,匹配儿子的两个线头的。

所以DP时维护un[son][i]表示儿子son当前网上接的第i个线头。

然后DP时先优先匹配root和一个带线头的儿子,然后把儿子子树的线头都清理掉(因为已经用掉了)

然后考虑其他儿子,很难DP,考虑状压。

先预处理link[x][y]=true/false表示儿子x和y是否有连边。

然后直接状压DP。

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

const int N = 1010;
const int D = 15;

int T, n, m;
int dp[N];         
int f[1<<D];            
int cnt, head[N], nxt[N<<1], to[N<<1]; 
bool vis[N][N];         
vector<int> un[N];      

int parentArr[N], depthArr[N], sz[N], heavy[N], top[N], dfn[N], rnkNode[N], timerHLD;

int lowbit(int x) { return x & -x; }

void init_all() {
	cnt = 0;
	memset(head, 0, sizeof(head));
	memset(vis, 0, sizeof(vis));
	memset(dp, 0, sizeof(dp));
	timerHLD = 0;
	for (int i = 0; i < N; ++i) {
		parentArr[i] = depthArr[i] = sz[i] = heavy[i] = top[i] = dfn[i] = rnkNode[i] = 0;
		heavy[i] = -1;
	}
}

void adde(int u, int v) {
	to[++cnt] = v;
	nxt[cnt] = head[u];
	head[u] = cnt;
}

void hld_dfs1(int u, int fa) {
	parentArr[u] = fa;
	depthArr[u] = (fa ? depthArr[fa] + 1 : 0);
	sz[u] = 1;
	int maxsz = 0;
	for (int e = head[u]; e; e = nxt[e]) {
		int v = to[e];
		if (v == fa) continue;
		hld_dfs1(v, u);
		sz[u] += sz[v];
		if (sz[v] > maxsz) {
			maxsz = sz[v];
			heavy[u] = v;
		}
	}
}

void hld_dfs2(int u, int topf) {
	top[u] = topf;
	dfn[u] = ++timerHLD;
	rnkNode[timerHLD] = u;
	if (heavy[u] != -1) {
		hld_dfs2(heavy[u], topf);
	}
	for (int e = head[u]; e; e = nxt[e]) {
		int v = to[e];
		if (v == parentArr[u] || v == heavy[u]) continue;
		hld_dfs2(v, v);
	}
}

int lca(int a, int b) {
	while (top[a] != top[b]) {
		if (depthArr[top[a]] < depthArr[top[b]]) swap(a, b);
		a = parentArr[top[a]];
	}
	return depthArr[a] < depthArr[b] ? a : b;
}

void solve_dfs(int u, int fa) {
	int son[D], tot = 0;
	for (int e = head[u]; e; e = nxt[e]) {
		int v = to[e];
		if (v == fa) continue;
		solve_dfs(v, u);
		dp[u] += dp[v];       
		if (tot < D) son[tot++] = v; 
	}
	
	for (int i = 0; i < tot; ++i) {
		int ch = son[i];
		for (int j = 0; j < (int)un[ch].size(); ++j) {
			int x = un[ch][j];
			if (vis[x][u]) {
				dp[u]++;         
				un[ch].clear();  
				break;         
			}
		}
	}
	
	static bool link[D][D];
	for (int i = 0; i < tot; ++i)
		for (int j = 0; j < tot; ++j)
			link[i][j] = false;
	
	for (int i = 0; i < tot; ++i) {
		for (int j = i + 1; j < tot; ++j) {
			int a = son[i], b = son[j];
			bool found = false;
			for (int p = 0; p < (int)un[a].size(); ++p) {
				for (int q = 0; q < (int)un[b].size(); ++q) {
					if (vis[un[a][p]][un[b][q]]) {
						link[i][j] = link[j][i] = true;
						found = true;
						break;
					}
				}
				if (found) break;
			}
		}
	}
	
	int maxMask = (1 << tot) - 1;
	f[0] = 0;
	for (int mask = 1; mask <= maxMask; ++mask) {
		f[mask] = f[mask & (mask - 1)];
		int a = __builtin_ctz(mask);
		for (int b = a + 1; b < tot; ++b) {
			if ( ((mask >> b) & 1) && link[a][b] ) {
				int newmask = mask ^ (1<<a) ^ (1<<b);
				f[mask] = max(f[mask], f[newmask] + 1);
			}
		}
	}
	
	dp[u] += f[maxMask]; 
	
	un[u].clear();
	un[u].push_back(u);
	for (int i = 0; i < tot; ++i) {
		if (f[maxMask] == f[maxMask ^ (1<<i)]) {
			for (int x : un[son[i]]) un[u].push_back(x);
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> T;
	while (T--) {
		init_all();
		cin >> n;
		for (int i = 1; i <= n; ++i) un[i].clear();
		
		for (int i = 1; i < n; ++i) {
			int u, v;
			cin >> u >> v;
			adde(u, v);
			adde(v, u);
		}
		
		hld_dfs1(1, 0);
		hld_dfs2(1, 1);
		
		cin >> m;
		for (int i = 0; i < m; ++i) {
			int u, v;
			cin >> u >> v;
			vis[u][v] = vis[v][u] = true;
		}
		
		solve_dfs(1, 0);
		
		cout << dp[1] << '\n';
	}
	return 0;
}

图片

posted @ 2025-10-12 07:50  Dreamers_Seve  阅读(10)  评论(0)    收藏  举报