若干构造总结
构造一个通用的方法就是找特殊,去找出一些比较简单、便于构造的方案。
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;
}
浙公网安备 33010602011771号