• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
The blog of EzSun
lyy && ?
博客园    首页    新随笔    联系   管理    订阅  订阅

若干构造总结

构造一个通用的方法就是找特殊,去找出一些比较简单、便于构造的方案。

P10178 陌路寻诗礼

我们可以先把所有边权设为 \(1\)。
然后去跑一遍 \(bfs\)。
如果出现了等长的路径,就将路径中最后一条路径的长度 \(+1\)。
可以发现这样一定是最终路径最大值最小的。
最后检查路径长度最大值是否大于 \(k\) 即可。

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 3e5 + 7, M = 3e5 + 7, inf = 0x3f3f3f3f;
int Task;
int n, m, k;
int dis[N], ans[N];
int q[N], hh, tt;
struct Edge{
	struct edge{
		int to, pre, id;
	}e[M];
	int head[N], tot;
	void init(){
		tot = 0;
		memset(head, 0, sizeof(head));
	}
	void add(int x, int y, int i){
		e[++tot] = {y, head[x], i};
		head[x] = tot;
	}
	bool bfs(){
		for(int i = 1; i <= n; i++) dis[i] = inf, q[i] = 0;
		hh = 1, tt = 0;
		q[++tt] = 1;
		dis[1] = 0;
		while(hh <= tt){
			int u = q[hh++];
			//printf("%d:%d\n", u, dis[u]);
			for(int i = head[u]; i; i = e[i].pre){
				int v = e[i].to;
				//printf("v:%d,%d\n", v, dis[v]);
				if(dis[v] > dis[u] + 1){
					dis[v] = dis[u] + 1;
					q[++tt] = v;
					//printf("ok\n");
				}
				else if(dis[v] == dis[u] + 1){
					ans[e[i].id]++;
				}
			}
		}
		for(int i = 1; i <= m; i++){
			if(ans[i] > k) return false;
			//else printf("%d:%d\n", i, ans[i]);
		}
		return true;
	}
}E;
void Main(){
	E.init();
	cin >> n >> m >> k;
	for(int i = 1; i <= m; i++){
		int x, y;
		cin >> x >> y;
		E.add(x, y, i);
	}
	for(int i = 1; i <= m; i++) ans[i] = 1;
	bool t = E.bfs();
	if(!t){
		printf("No\n");
	}
	else{
		printf("Yes\n");
		for(int i = 1; i <= m; i++){
			printf("%d ", ans[i]);
		}
		puts("");
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> Task;
	while(Task--) Main();
	return 0; 
}

B4225 [常州市赛 2024] 黑板

对于序列 \([l,r]\),可以通过先合并 \([l,l+2]\),再依次合并最左边两个数,得到 \(r-1\),以此类推,还可以得到 \(l+1\)。
显然当 \(x=a\) 或 \(x=b\) 时无解。
接下来我们来分类讨论一下。
若 \(x\) 在不在两边四个位置,可以通过合并其两侧的序列得到 \(x-2\) 和 \(x+2\),再合并即可。
若 \(x=a+1\) 或 \(x=b-1\) ,则可以从 \(x\) 所在的一侧依次合并除了 \(x\) 外最靠近那一侧的两个数,可以得到一个 \(x\),再与原本的 \(x\) 合并即可。
接下来是稍复杂的一种情况:\(x=a+2\) 或 \(x=b-2\)。
需要分 \(b-a\) 的奇偶性讨论一下。
若为偶数,则先合并 \(x\) 那一侧的第 \(2,3\) 个,随后再从异侧一直合过去即可。
若为奇数,对于\(x=2\), 则先合并 \(a,b-1\),得到 \(\frac{a+b-1}{2}\),再直接消除直到用掉原有的 \(\frac{a+b-1}{2}\) 并得到 \(\frac{a+b+1}{2}\),再按照上文提到的方式合并即可。

code:

#include<stdio.h>
int a,b,x;
int main()
{
    scanf("%d%d%d",&a,&b,&x);
    b-=a;
    x-=a;
    if((b-x)*x)
    {
        if(x==1)
        {
            printf("%d %d\n",b-2,b);
            for(a=0;a<b-1;++a)
            {
                printf("%d %d\n",b-2-a,b-1-a);
            }
        }
        else if(x==b-1)
        {
            printf("%d %d\n",0,2);
            printf("%d %d\n",0,1);
            for(a=0;a<b-2;++a)
            {
                printf("%d %d\n",0,a+3);
            }
        }
        else if(x==2)
        {
            printf("%d %d\n",0,b-b%2);
            printf("%d %d\n",b-3+b%2,b-1+b%2);
            for(a=0;a<b-b/2-1;++a)
            {
                printf("%d %d\n",b-3-a,b-2-a);
            }
            printf("%d %d\n",0,b/2-1);
            for(a=0;a<b/2-2;++a)
            {
                printf("%d %d\n",0,b/2-2-a);
            }
        }
        else if(x==b-2)
        {
            printf("%d %d\n",b%2,b);
            printf("%d %d\n",1-b%2,3-b%2);
            printf("%d %d\n",1-b%2,2+b%2);
            for(a=0;a<b-b/2-2;++a)
            {
                printf("%d %d\n",1-b%2,a+4);
            }
            printf("%d %d\n",0,1);
            for(a=0;a<b/2-2;++a)
            {
                printf("%d %d\n",0,b/2+b%2+2+a);
            }
        }
        else
        {
            printf("%d %d\n",0,2);
            printf("%d %d\n",0,1);
            printf("%d %d\n",b-2,b);
            for(a=0;a<x-3;++a)
            {
                printf("%d %d\n",0,a+3);
            }
            for(a=0;a<b-x-2;++a)
            {
                printf("%d %d\n",b-2-a,b-1-a);
            }
            printf("%d %d\n",0,x+1);
            printf("%d %d\n",0,x);
        }
    }
    else
    {
        putchar(45);
        putchar(49);
    }
}
摘自 https://www.luogu.com.cn/article/ezpfpxjr

P7854 「EZEC-9」GCD Tree

不难发现,所有数的倍数必须为该数的后代。
那我们便可以在 \(O(N log V)\) 的时间内构建出树。
但可能会出现 \(gcd\) 未出现的情况。
如序列 \([2,8,12]\),此时 \(gcd(8,12)=4\),应为无解。
我们可以考虑枚举 \(gcd\),并寻找其倍数。
如果有不止一个点是该数的倍数且其父亲不是该数的倍数,那么就一定无解。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 7, V = 2e6 + 17;
int n;
int a[N];
int pos[V];
int fa[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		if(pos[a[i]]) fa[pos[a[i]]]=i;
		pos[a[i]]=i;	
	}
	for(int i = 1; i <= n; i++){
		if(fa[i]) continue;
		for(int j = 2; j * j <= a[i]; j++){
			if(a[i] % j != 0) continue;
			if(!pos[j] && !pos[a[i] / j]) continue;
			if(pos[j]){
				if(!fa[i]){
					fa[i] = pos[j];
				}
				else{
					if(max(j, a[fa[i]]) % min(j, a[fa[i]]) != 0){
						printf("-1\n");
						return 0;
					}
					else{
						fa[i] = j > a[fa[i]] ? pos[j] : fa[i];
					}
				}
			}
			if(j * j == a[i]) continue;
			if(pos[a[i] / j]){
				if(!fa[i]){
					fa[i] = pos[a[i] / j];
				}
				else{
					if(max(a[i] / j, a[fa[i]]) % min(a[i] / j, a[fa[i]]) != 0){
						printf("-1\n");
						return 0;
					}
					else{
						fa[i] = a[i] / j > a[fa[i]] ? pos[a[i] / j] : fa[i];
					}
				}
			}
		}
		if(!fa[i] && pos[1] && a[i] != 1){
			fa[i] = pos[1];
		}
	}
	for(int i = 1, flg = 0; i <= n; i++){
		if(fa[i] == 0) flg++;
		if(flg > 1){
			printf("-1\n");
			return 0;
		}
	}
	for(int i = 1; i <= V - 17; i++){
		int tot = 0;
		for(int j = i; j <= V - 17; j += i){
			if(pos[j] && (!fa[pos[j]] || a[fa[pos[j]]] % i)) tot++;
		}
		if(tot > 1){
			printf("-1\n");
			return 0;
		}
	}
	for(int i = 1; i <= n; i++){
		printf("%d ", fa[i]);
	}
	return 0; 
}

P11762 [IAMOI R1] 走亲访友

不难发现最后需要留下一棵生成树
我们可以先把生成树处理出来
问题便转化为了用一条路径将非树边全部删除
不难想到欧拉回路
在生成树上 \(dfs\)
如果该节点的度数为奇数
就将其与其父亲连一条边
由于一条边会增加 \(2\) 个度数
最终根节点的度数也一定为偶
由于每个点最多连一条边
因此最终的边数至多为 \(n+m-1\)

Code:

#include<bits/stdc++.h>
using namespace std;
#define il inline
const int N = 1e3 + 7, M = 4e6 + 7;
int dg[N];
int n, m, k, s;
bool vis[N], used[(M + N) << 1];
vector<int>son[N];
vector<pair<int, int> >ans;
int cut[M];
int cnt;
struct Edge{
	int head[N], tot = 1;
	struct edge{
		int to, pre, id;
	}e[M << 1];
	il void add(int x, int y, int i){
		e[++tot] = {y, head[x], i};
		head[x] = tot;
	}
	il void dfs1(int u, int fa, int id){
		vis[u] = 1;
		for(int i = head[u]; i; i = e[i].pre){
			int v = e[i].to;
			if(!vis[v]){
				son[u].push_back(v);
				cut[e[i].id] = 1;
				dfs1(v, u, e[i].id);
			}
		}
		if(dg[u] & 1){
			add(fa, u, id);
			add(u, fa, id);
			dg[u]++;
			dg[fa]++;
		}
	}
	il void dfs3(int u, int id){
		for(int &i = head[u]; i; i = e[i].pre){
			if(used[i]) continue;
			int v = e[i].to;
			used[i] = used[i ^ 1] = 1;
			dfs3(v, e[i].id);
		}
		if(id) ans.push_back({id, cut[id]});
	}
}E;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> k >> s;
	cnt = m;
	for(int i = 1; i <= m; i++){
		int u, v;
		cin >> u >> v;
		if(u > v) swap(u, v);
		dg[u]++;
		dg[v]++;
		E.add(u, v, i);
		E.add(v, u, i);
	}
	E.dfs1(s, 0, 0);
	E.dfs3(s, 0);
	printf("%d\n", ans.size());
	for(auto a : ans){
		printf("%d %d\n", a.first, a.second);
	}
	return 0; 
}

P14975 [USACO26JAN1] COW Splits B

不难,但赛时读错题虚空推理了 \(0.8\)h。
无解的条件为 \(n\) 为奇数。
容易发现必定可以在 \(3\) 次操作以内完成,即每次删去一种字母。
考虑如何在两次操作以内完成。
不难发现 \(cow\),\(wco\),\(owc\) 中两两的公共子序列长度为 \(2\)。
那么可以将序列从中间一分为二,对于两边对应的每三个子串,便在第一次删去他们的公共子序列,第二次删去剩下的一个字母。
因此最小操作次数为 \(2\)。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int Task;
int n;
string s;
int ans[N * 3];
void Main(){
	cin >> n >> s;
	s = ' ' + s;
	if(n & 1){
		printf("-1\n");
		return;
	}
	int half = 3 * (n / 2);
	if(s.substr(1, half) == s.substr(half + 1, half)){
		printf("1\n");
		for(int i = 1; s[i]; i++){
			printf("1 ");
		}
		puts("");
		return;
	}
	for(int i = 1; i <= half; i += 3){
		string s1 = s.substr(i, 3), s2 = s.substr(i + half, 3);
		if(s1 == s2){
			ans[i] = ans[i + 1] = ans[i + 2] = ans[i + half] = ans[i + 1 + half] = ans[i + 2 + half] = 1;
			continue;
		}
		if(s1 == "COW"){
			if(s2 == "WCO"){
				ans[i] = ans[i + 1] = ans[i + half + 1] = ans[i + half + 2] = 1;
				ans[i + 2] = ans[i + half] = 2;
			}
			if(s2 == "OWC"){
				ans[i + 1] = ans[i + 2] = ans[i + half] = ans[i + half + 1] = 1;
				ans[i] = ans[i + half + 2] = 2;
			}
		}
		if(s1 == "WCO"){
			if(s2 == "COW"){
				ans[i + 1] = ans[i + 2] = ans[i + half] = ans[i + half + 1] = 1;
				ans[i] = ans[i + half + 2] = 2;
			}
			if(s2 == "OWC"){
				ans[i] = ans[i + 1] = ans[i + half + 1] = ans[i + half + 2] = 1;
				ans[i + 2] = ans[i + half] = 2;
			}
		}
		if(s1 == "OWC"){
			if(s2 == "COW"){
				ans[i] = ans[i + 1] = ans[i + half + 1] = ans[i + half + 2] = 1;
				ans[i + 2] = ans[i + half] = 2;
			}
			if(s2 == "WCO"){
				ans[i + 1] = ans[i + 2] = ans[i + half] = ans[i + half + 1] = 1;
				ans[i] = ans[i + half + 2] = 2;
			}
		}
	}
	printf("2\n");
	for(int i = 1; i <= 3 * n; i++){
		printf("%d ", ans[i]);
	}
	puts("");
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int lyy;
	cin >> Task >> lyy;
	while(Task--) Main();
	return 0;
}

posted @ 2026-05-11 13:19  EzSun599  阅读(4)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3