2022NOIPA层联测2

二分图排列

不想写了

image

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 1000005;

int n, q, a[maxn];
int f[maxn][2], tmp[4][2];
void solve(){
	for(int i = 1; i <= n; ++i)f[i][0] = f[i][1] = -maxn;
	f[n][0] = f[n][1] = maxn;
	for(int i = n; i > 1; --i){
		tmp[0][0] = -a[i]; tmp[0][1] = f[i][0];
		tmp[1][0] = a[i]; tmp[1][1] = f[i][1];
		tmp[2][0] = f[i][0]; tmp[2][1] = -a[i];
		tmp[3][0] = f[i][1]; tmp[3][1] = a[i];
		for(int j = 0; j <= 1; ++j){
			int now = j ? a[i - 1] : -a[i - 1];
			for(int k = 0; k <= 3; ++k)if(now < tmp[k][0])f[i - 1][j] = max(f[i - 1][j], tmp[k][1]);
		}
		if(f[i - 1][0] == f[i - 1][1] && f[i - 1][0] == -maxn){
			printf("NO\n");
			return;
		}
	}
	printf("YES\n");
	int p1 = -maxn, p2 = -maxn;
	for(int i = 1; i <= n; ++i){
		if(-a[i] > p1)p1 = a[i] = -a[i];
		else if(-a[i] > p2 && -a[i] < f[i][0])p2 = a[i] = -a[i];
		else p1 = a[i];
		if(p1 < p2)swap(p1, p2);
	}
	for(int i = 1; i <= n; ++i)printf("%d ",a[i]);
	printf("\n");
}
int main(){
	q = read();
	for(int ask = 1; ask <= q; ++ask){
		n = read();
		for(int i = 1; i <= n; ++i)a[i] = read();
		solve();
	}
	return 0;
}

最短路问题 V3

水,但是人傻,给 \(q\) 带上了 \(44log(44 * 43 / 2)\) 的常数

\(TLEcoders\) 上你不 \(T\)\(T\)

建出树,非树边建新图,特殊点最多 \(42\) 个,于是每次处理询问点和他们之间的最短路即可

但是其实可以更加优化常数

从特殊点跑到所有点最短路,然后枚举经过了哪个特殊点,或者树上路径即可

code
#pragma GCC optimize(3)

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 100005;
typedef pair<int,ll> pil;
vector<pil>g[maxn];
struct edge{int to, net, val;}e[maxn << 1 | 1];
int head[maxn], tot;
void add(int u, int v, int w){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
	e[tot].val = w;
}
int n, m, q;
int f[maxn];
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
int t[maxn], cnt;
ll sum[maxn];
int dep[maxn], size[maxn], son[maxn], top[maxn];
void dfs1(int x){
	size[x] = 1;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == f[x])continue;
		f[v] = x; dep[v] = dep[x] + 1; sum[v] = sum[x] + e[i].val;
		dfs1(v);
		size[x] += size[v];
		son[x] = size[son[x]] > size[v] ? son[x] : v; 
	}
}
void dfs2(int x, int tp){
	top[x] = tp;
	if(son[x])dfs2(son[x], tp);
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == f[x] || v == son[x])continue;
		dfs2(v, v);
	}
}
int lca(int u, int v){
	while(top[u] != top[v]){
		if(dep[top[u]] < dep[top[v]])swap(u, v);
		u = f[top[u]];
	}
	if(dep[u] < dep[v])return u;
	return v;
}
ll di[maxn];
bool vis[maxn], ts[maxn];
typedef pair<ll, int> pli;
priority_queue <pli, vector<pli>, greater<pli> >Q;
void solve(){
	int u = read(), v = read();
	if(!ts[u])
	for(int i = 1; i <= cnt; ++i){
		int lc = lca(u, t[i]);
		ll dis = sum[u] + sum[t[i]] - sum[lc] - sum[lc];
		g[u].push_back(pil(t[i], dis));
		g[t[i]].push_back(pil(u, dis));
	}
	if(!ts[v])
	for(int i = 1; i <= cnt; ++i){
		int lc = lca(v, t[i]);
		ll dis = sum[v] + sum[t[i]] - sum[lc] - sum[lc];
		g[v].push_back(pil(t[i], dis));
		g[t[i]].push_back(pil(v, dis));
	}
	di[u] = 0; Q.push(pli{0, u});

	while(!Q.empty()){
		int x = Q.top().second; Q.pop();
		if(vis[x])continue;
		vis[x] = 1;
		for(pil now : g[x]){
			if(di[now.first] > di[x] + now.second){
				di[now.first] = di[x] + now.second;
				Q.push(pli(di[now.first], now.first));
			}
		}
	}
	int lc = lca(u, v);
	ll ans =  min(di[v], sum[u] + sum[v] - sum[lc] - sum[lc]);
	printf("%lld\n",ans);
	for(int i = 1; i <= cnt; ++i)di[t[i]] = di[0], vis[t[i]] = 0;
	di[u] = di[v] = di[0]; vis[u] = vis[v] = 0;
	if(!ts[u])
	for(int i = 1; i <= cnt; ++i){
		g[u].pop_back();
		g[t[i]].pop_back();
	}
	if(!ts[v])
	for(int i = 1; i <= cnt; ++i){
		g[v].pop_back();
		g[t[i]].pop_back();
	}
}
signed main(){
	n = read(), m = read();
	for(int i = 1; i <= n; ++i)f[i] = i;
	for(int i = 1; i<= m; ++i){
		int u = read(), v = read(), w = read();
		if(fa(u) == fa(v)){
			t[++cnt] = u; t[++cnt] = v;
			ts[u] = ts[v] = 1;
			g[u].push_back(pil(v, w));
			g[v].push_back(pil(u, w));

		}else{
			add(u, v, w); add(v, u, w);
			f[fa(u)] = fa(v);
		}
	}
	sort(t + 1, t + cnt + 1); cnt = unique(t + 1, t + cnt + 1) - t - 1;
	for(int i = 1; i <= n; ++i)f[i] = 0;
	dfs1(1); dfs2(1, 1);
	for(int i = 1; i <= cnt; ++i)
		for(int j = i + 1; j <= cnt; ++j){
			int lc = lca(t[i], t[j]);
			ll dis = sum[t[i]] + sum[t[j]] - sum[lc] - sum[lc];
			g[t[i]].push_back(pil(t[j], dis));
			g[t[j]].push_back(pil(t[i], dis));
		}
	memset(di, 0x3f, sizeof(di));
	q = read();
	for(int i = 1; i <= q; ++i)solve();
	return 0;
}

捡石子游戏

考虑 \(n = 2\) 发现只有放在 \(w\) 大于旁边的才会胜利

扩展下去,我们只会沿着一条递增的路径走,当周围没有比他大的点时,该位置先手必败

于是对每个起始点 \(DFS\) 下去看递增链长度即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 3005;
int n, w[maxn], head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
bool dfs(int x, int fa){
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		if(w[x] > w[v] && dfs(v, x) == false)return true;
	}
	return false;
}
int main(){
	n  = read();
	for(int i = 1; i <= n; ++i)w[i] = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	bool fl = 0;
	for(int i = 1; i <= n; ++i)if(dfs(i, 0)){
		printf("%d ", i);
		fl = 1;
	}
	if(!fl)printf("-1\n");
	return 0;
}``cpp

凹函数

如果你想拟合个什么函数,就不要想了

转化成选择斜率不同的坐标都为正数,且 \(gcd(x, y) == 1\) 的向量

至于函数是啥,不重要

进一步转化发现把恰好 \(n, m\) 变成至多 \(n, m\) 没有影响

于是就有了朴素的背包 \(dp\)

一个比较经典的套路,发现答案范围小于坐标范围,于是交换下标和维护值,这个背包显然能够做到

于是,我们再加入一个剪枝,选择一个 \((x, y)\) 之前 \((a, b) a <= x, b <= y\) 一定被选

于是对满足 \(a <= x , b <= y\) 的向量求和,如果 \(>= n\) 那么该向量不能选择

于是通过一些证明得到此时向量的数量为 \(n^{2/ 3}\) 于是可以通过此题

证明不会

image

code
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 10005;
const int inf = 0x3f3f3f3f;
int f[4005][405], size[4005], sum[4005][2], mx;
void modify(int x, int y){
	for(int i = mx; i >= x; --i){
		for(int j = 0; j <= size[i - x]; ++j){
			int now = f[i - x][j] + y;
			if(now > mx)break;
			f[i][j + 1] = min(f[i][j + 1], now);
		}
		if(f[i][size[i] + 1] < inf)f[i][++size[i] + 1] = inf;
	}
}
int solve(int n, int m){return upper_bound(f[n], f[n] + size[n] + 1, m) - f[n];}
void pre(){
	for(int i = 0; i <= mx; ++i)f[i][1] = inf;
	for(int i = 1; i <= mx; ++i){
		int sx = 0, sy = 0;
		for(int j = 1; j <= mx && max(sum[j][0], sum[j][1]) <= mx; ++j){
			if(__gcd(i, j) == 1){
				sx += i; sy += j;
				if(max(sum[j][0] + sx, sum[j][1] + sy) <= mx)modify(i, j);
			}
			sum[j][0] += sx;
			sum[j][1] += sy;
		}
	}
}
int n[maxn], m[maxn];
int main(){
	int q = read();
	for(int i = 1; i <= q; ++i){
		n[i] = read(), m[i] = read();
		mx = max(max(n[i], m[i]), mx);
	}
	pre();
	for(int i = 1; i <= q; ++i)printf("%d\n",solve(n[i], m[i]));
	return 0;
}
posted @ 2022-10-03 07:29  Chen_jr  阅读(39)  评论(15编辑  收藏  举报