Chapter 5. Graph Theory:: Fundamentals:: Intermediate

时间:2022-10-30 19:33:47

10457 - Magic Car

题意一开始看起来有点费解,其实最后就是要起点到终点的路径上最大边与最小边之差越小越好。这样我们可以先将边排个序,然后枚举路径上的最小边,之后依次将比它大的边按升序的顺序添加,直到起点和重点连通为止,这样就得到了一个可行解并尝试去更新记录的最优解。等所有可能的最小边都枚举完之后就可以得到最终的最优解了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 210
#define MAXM 1010
#define INF 0x7fffffff
int N, M, SE, EE, p[MAXN];
struct Edge
{
int u, v, w;
bool operator < (const Edge &t) const
{
return w < t.w;
}
}e[MAXM];
int find(int x)
{
return p[x] == x ? x : (p[x] = find(p[x]));
}
void merge(int x, int y)
{
int tx = find(x), ty = find(y);
if(tx != ty) p[tx] = ty;
}
void input()
{
for(int i = ; i < M; i ++)
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
scanf("%d%d", &SE, &EE);
}
void process()
{
std::sort(e, e + M);
int k;
scanf("%d", &k);
while(k --)
{
int s, t;
scanf("%d%d", &s, &t);
int ans = INF;
for(int i = ; i < M; i ++)
{
int j;
for(int j = ; j <= N; j ++) p[j] = j;
for(j = i; j < M; j ++)
{
merge(e[j].u, e[j].v);
if(find(s) == find(t)) break;
}
if(j != M) ans = std::min(ans, e[j].w - e[i].w);
}
printf("%d\n", ans + SE + EE);
}
}
int main()
{
while(scanf("%d%d", &N, &M) == )
{
input();
process();
}
return ;
}

10607 - Siege

又是一个神一样的题意,我再次没读懂,不过根据种种迹象来揣测的话,应该是可以先占领边疆城市,然后就可以占领和已占领的城市相邻的城市了,直到能够围住首都。这个题目里面有个比较特殊的条件,就是“There is no point in Flatland, which has a boundary with more than three other provinces i.e. four squares, which have the same vertex can’t belong to four different provinces.”,实际上这个条件保证了包围A的所有城市是连通的。既然包围A的城市是连通的,那么只要考虑从边疆开始占领到任意一个包围A的城市就可以了,最后在加上包围A的城市的数量就行了。于是我们可以把所有包围A的城市看成一个起点S,将外界看成终点T,然后相邻的两个城市连边,最后求S到T的最短路就可以了。

这个题目的坑还是很多的,首先就是什么时候输出-1,我想来想去,最后还是在UVA的论坛上找到了一种情况:

5 5

CCCCC

CAAAC

CABAC

CAAAC

CCCCC

这种情况就应当输出-1了,因为根据题意B应该是和A相邻的,但是我们没办法通过给定的规则占领B,所以输出-1。

其次,题目的数据里面是有空格的,还有一些>127的ASC码,这些比较特殊的字符也全当正常的城市去处理就好了。

#include<stdio.h>
#include<string.h>
int N, M, g[][], first[], e, next[], v[], cnt, p[], q[], dis[];
char b[][];
bool adj[];
const int S = , T = ;
const int dx[] = {-, , , }, dy[] = {, , -, };
int find(int x)
{
return p[x] == x ? x : (p[x] = find(p[x]));
}
void merge(int x, int y)
{
int tx = find(x), ty = find(y);
if(tx != ty) p[tx] = ty;
}
bool inside(int x, int y)
{
return x >= && x < N && y >= && y < M;
}
void add(int x, int y)
{
v[e] = y;
next[e] = first[x], first[x] = e ++;
}
void process()
{
cnt = ;
memset(adj, , sizeof(adj));
for(int i = ; i < N; i ++)
for(int j = ; j < M; j ++)
if(b[i][j] == 'A')
{
for(int k = ; k < ; k ++)
{
int ni = i + dx[k], nj = j + dy[k];
if(!inside(ni, nj)) continue;
int c = (int)b[ni][nj] & ;
if(c != 'A' && !adj[c]) ++ cnt, adj[c] = true;
}
} memset(g, , sizeof(g));
memset(first, -, sizeof(first)), e = ;
for(int i = ; i < ; i ++) p[i] = i;
for(int i = ; i < N; i ++)
for(int j = ; j < M; j ++)
{
int c = (int)b[i][j] & ;
if(c == 'A' || c <= ' ') continue;
for(int k = ; k < ; k ++)
{
int ni = i + dx[k], nj = j + dy[k], nc;
if(!inside(ni, nj)) nc = T;
else nc = (int)b[ni][nj] & ; if(nc == 'A' || c == nc) continue; if(adj[c] && adj[nc])
{
merge(c, nc);
continue;
} if(adj[c]) c = S;
else if(adj[nc]) nc = S; if(!g[c][nc])
{
g[c][nc] = ;
add(c, nc), add(nc, c);
}
}
} int node = -;
for(int i = ; i < ; i ++)
if(adj[i])
{
if(node == -) node = find(i);
else if(node != find(i))
{
printf("-1\n");
return ;
}
} int rear = ;
memset(dis, -, sizeof(dis));
dis[S] = ;
q[rear ++] = S;
for(int i = ; i < rear; i ++)
for(int j = first[q[i]]; j != -; j = next[j])
if(dis[v[j]] == -)
dis[v[j]] = dis[q[i]] + , q[rear ++] = v[j]; printf("%d\n", dis[T] > ? dis[T] - + cnt : -);
}
int main()
{
char s[];
for(;;)
{
gets(s);
sscanf(s, "%d%d", &N, &M);
if(N == && M == ) break;
for(int i = ; i < N; i ++) gets(b[i]);
process();
}
return ;
}

10798 - Be wary of Roses

由于要综合考虑4种情况,那么每走一步,我们可以看作4种情况各走了一步,而且其他3个位置是可以根据当前位置计算出来,这样每一步我们都可以知道哪种情况会多踩一朵花。为了能够完整的记录下此时此刻的状态,我们可以将4种情况各自经过的花的数量记录到状态中,同时将当前所在的位置记录到状态中。之后我们只要留意哪些状态可以走出去并选出其中最优的状态即可。这样我们实际上只需要关注哪些状态可达就可以了,于是用BFS就可以搞定了。

由于一开始交上去TLE了,所以适当地做了一些常数优化,比如用手写队列替代STL的队列等等。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 25
#define INF 0x3f3f3f3f
int N, a[MAXN][MAXN], vis[MAXN][MAXN][], R;
const int dx[] = {-, , , }, dy[] = {, , -, }, fac[] = {, , , };
int getMax(int a[])
{
int ans = a[];
for(int i = ; i < ; i ++) ans = std::max(ans, a[i]);
return ans;
}
struct St
{
int x, y, a[], max, code;
St(){}
St(int _x, int _y) : x(_x), y(_y)
{
code = max = ;
memset(a, , sizeof(a));
}
}q[];
void input()
{
char s[MAXN];
for(int i = ; i < N; i ++)
{
scanf("%s", s);
for(int j = ; j < N; j ++)
a[i][j] = s[j] == 'R';
}
}
bool inside(int x, int y)
{
return x >= && x < N && y >= && y < N;
}
void process()
{
const int ox = N >> , oy = N >> , most = N >> ;
int front = , rear = ;
vis[ox][oy][] = R, q[rear ++] = St(ox, oy);
int ans = INF;
while(front < rear)
{
St st = q[front ++];
if(st.max >= ans) continue; for(int i = ; i < ; i ++)
{
int x = st.x + dx[i], y = st.y + dy[i];
if(!inside(x, y))
{
ans = std::min(ans, st.max);
continue;
} int bx = x - ox, by = y - oy;
St nst = st;
nst.x = x, nst.y = y;
if(a[bx + ox][by + oy]) ++ nst.a[], nst.code += fac[];
if(a[-bx + ox][-by + oy]) ++ nst.a[], nst.code += fac[];
if(a[-by + ox][bx + oy]) ++ nst.a[], nst.code += fac[];
if(a[by + ox][-bx + oy]) ++ nst.a[], nst.code += fac[];
nst.max = getMax(nst.a); if(nst.max > most) continue; if(vis[x][y][nst.code] != R) vis[x][y][nst.code] = R, q[rear ++] = nst;
}
}
printf("At most %d rose(s) trampled.\n", ans);
}
int main()
{
R = ;
while(scanf("%d", &N), N)
{
++ R;
input();
process();
}
return ;
}

10941 - Words adjustment

这个题目可以等价成一开始有一个字符串s,然后选出另一个字符串t做减法,并将剩余的字符串看做新的s,如此反复进行,直到s的长度为0为止。这样做的复杂度主要取决于做减法的时候一共能拓展出多少个新的字符串,这个值的上限我没有仔细算,不过至少这样做可以AC这个题目。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<set>
#include<queue>
#define MAXN 1010
class State
{
public:
int d;
std::string s;
State() {}
State(int d, std::string s)
{
this->d = d, this->s = s;
}
}q[];
const int Q = ;
int N;
std::string x, y, s[MAXN];
void input()
{
std::cin >> x >> y;
std::cin >> N;
for(int i = ; i < N; i ++)
std::cin >> s[i];
}
void process()
{
int front, rear;
front = rear = ;
if(x.size() < y.size()) std::swap(x, y);
if(x == y)
{
std::cout << "0\n";
return ;
}
int n = y.size();
if(x.substr(, n) != y)
{
std::cout << "-1\n";
return ;
}
std::set<std::string> set;
std::string a = x.substr(n, x.size() - n);
q[rear ++] = State(, a), set.insert(a); while(front != rear)
{
State st = q[front ++];
if(front >= Q) front = ;
n = st.s.size();
for(int i = ; i < N; i ++)
{
if(st.s == s[i])
{
std::cout << st.d << "\n";
return ;
}
std::string a;
bool over = false;
int nn = s[i].size();
if(n <= nn)
{
if(s[i].substr(, n) != st.s) over = true;
else a = s[i].substr(n, nn - n);
}
else
{
if(st.s.substr(, nn) != s[i]) over = true;
else a = st.s.substr(nn, n - nn);
}
if(over) continue;
if(!set.count(a))
{
set.insert(a);
q[rear ++] = State(st.d + , a);
if(rear >= Q) rear = ;
}
}
}
std::cout << "-1\n";
}
int main()
{
int t;
std::cin >> t;
while(t --)
{
input();
process();
}
return ;
}

10968 - KuPellaKeS

题目中有说“至多两个节点”有奇数个邻居,那么自然就要分三种情况讨论。

在讨论之前我们先排除一种特殊情况,就是存在孤立点,这样的情况一定无解。

如果没有节点有奇数个邻居,那么直接输出0就可以了。

如果只有一个点有奇数个邻居,哦,其实不可能有这种情况。

如果有两个点有奇数个邻居,这种情况就是比较一般的情况了,我们不妨设两个点分别为s和t。

这时我们必须删掉和s相邻的一条边使得s变成一个有偶数个邻居的点,那么如果s和t之间有边,自然删掉这条边之后就OK了,但如果s和t没有边的话,这时又会制造一个新的有奇数个邻居的节点,于是又要重复上面的步骤。这样不难发现,最后删掉的所有边实际上组成了一条路径,而我们就是要这条路径越短越好,想到这自然就想到用最短路相关算法去求解了。不过还是有些细节需要注意,首先,如果s或t本身只有一个邻居,那么是无解的,这种情况应当特判掉,其次,我们删掉的这条路径不能经过只有两个邻居的节点,否则删掉这条路径后就会出现孤立点了,于是我们应当先将图中只有两个邻居的节点忽视掉再做最短路。

#include<stdio.h>
#include<string.h>
#define MAXN 1010
#define MAXM 2000010
#define INF 0x3f3f3f3f
int N, M, dgr[MAXN], first[MAXN], e, next[MAXM], v[MAXM], q[MAXN], dis[MAXN];
bool del[MAXN];
void add(int x, int y)
{
v[e] = y;
next[e] = first[x], first[x] = e ++;
}
void process()
{
int s = -, t = -;
memset(del, , sizeof(del[]) * (N + ));
for(int i = ; i <= N; i ++)
{
if(dgr[i] <= )
{
printf("Poor Koorosh\n");
return ;
}
if(dgr[i] == ) del[i] = true;
else if(dgr[i] & )
{
if(s == -) s = i;
else t = i;
}
}
if(s == -)
{
printf("0\n");
return ;
}
memset(dis, 0x3f, sizeof(dis[]) * (N + ));
int rear = ;
dis[s] = , q[rear ++] = s;
for(int i = ; i < rear; i ++)
for(int j = first[q[i]]; j != -; j = next[j])
if(!del[v[j]] && dis[q[i]] + < dis[v[j]])
dis[v[j]] = dis[q[i]] + , q[rear ++] = v[j];
if(dis[t] != INF) printf("%d\n", dis[t]);
else printf("Poor Koorosh\n");
}
int main()
{
while(scanf("%d%d", &N, &M), N)
{
memset(dgr, , sizeof(dgr[]) * (N + ));
memset(first, -, sizeof(first[]) * (N + )), e = ;
for(int i = ; i < M; i ++)
{
int x, y;
scanf("%d%d", &x, &y);
add(x, y), add(y, x);
++ dgr[x], ++ dgr[y];
}
process();
}
return ;
}

11165 - Galactic Travel

这个题目最基本的思路就是用BFS了,但是由于点的数量巨大,直接去BFS肯定会超时,但是我们仔细分析一下不难发现之所以会超时,是因为对于每个点我们都会尝试去扫描一遍所有与之相邻的点,但是如果每次我们都能只扫描那些还没有被访问的点的话自然就不会超时了。于是我们需要一种策略使得每次能快速地找到和当前的点相邻的点中还有哪些点是没有被访问的,这样的策略有很多种,比如并查集,线段树等等,我在这里使用了并查集来达到这个效果。

并查集中p[x]指向的是在标号大于等于x的节点中,最小的未访问的节点。

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<algorithm>
#define MAXN 100010
#define MAXK 41010
struct Seg
{
int u, v[];
bool operator < (const Seg &t) const
{
if(u != t.u) return u < t.u;
if(v[] != t.v[]) return v[] < t.v[];
return v[] < t.v[];
}
}fb[MAXK], seg[MAXN + MAXK];
int N, K, S, T, p[MAXN], dis[MAXN], first[MAXN], e, next[MAXN + MAXK], q[MAXN];
bool cal[MAXN];
void add(int u, Seg &s)
{
seg[e] = s;
next[e] = first[u], first[u] = e ++;
}
int stack[MAXN];
/**
int find(int x)
{
return p[x] == x ? x : (p[x] = find(p[x]));
} Because my OS is Windows, I choose the function below instead of it to avoid stack overflow.
*/
int find(int x)
{
int top = ;
while(p[x] != x) stack[top ++] = x, x = p[x];
while(top) p[stack[-- top]] = x;
return x;
}
void merge(int x, int y)
{
int tx = find(x), ty = find(y);
if(tx != ty) p[tx] = ty;
}
void input()
{
scanf("%d%d", &N, &K);
for(int i = ; i < K; i ++)
{
scanf("%d%d-%d", &fb[i].u, &fb[i].v[], &fb[i].v[]);
if(fb[i].v[] > fb[i].v[]) std::swap(fb[i].v[], fb[i].v[]);
}
scanf("%d%d", &S, &T);
}
void initSeg()
{
std::sort(fb, fb + K);
memset(first, -, sizeof(first[]) * N), e = ;
memset(cal, , sizeof(cal[]) * N);
int last = -, u;
Seg s;
for(int i = ; i < K; i ++)
{
u = fb[i].u, cal[u] = true;
if(fb[i].v[] > last + )
{
s.v[] = last + , s.v[] = fb[i].v[] - ;
add(u, s);
}
last = std::max(last, fb[i].v[]);
if(i == K - || fb[i + ].u != fb[i].u)
{
if(last + <= N - )
{
s.v[] = last + , s.v[] = N - ;
add(u, s);
}
last = -;
}
}
for(int i = ; i < N; i ++)
if(!cal[i])
{
s.v[] = , s.v[] = N - ;
add(i, s);
}
}
void process()
{
initSeg();
dis[T] = -;
for(int i = ; i <= N; i ++) p[i] = i;
int rear = ;
dis[S] = , q[rear ++] = S;
merge(S, S + );
for(int i = ; i < rear; i ++)
for(int j = first[q[i]]; j != -; j = next[j])
{
int x = seg[j].v[], y = seg[j].v[];
for(int u = find(x); u <= y; u = find(u))
{
dis[u] = dis[q[i]] + , q[rear ++] = u;
merge(u, u + );
}
} if(dis[T] == -) printf("Impossible\n");
else printf("%d\n", dis[T]);
}
int main()
{
int t;
scanf("%d", &t);
for(int tt = ; tt <= t; tt ++)
{
input();
printf("Case #%d: ", tt);
process();
}
return ;
}

11853 - Paintball

首先考虑无解的情况,那么必然是从上到下若干个圆连在了一起把路截断了。那么我们就可以将圆转化成点,并虚拟一个起点和终点,凡是和上边界相交的圆都和起点相连,和下边界相交的圆都和终点相连,并且相交的圆之间也连起来,这样如果从起点出发可以到达终点,那么途径的圆就会截断我们的通路。

解决完无解的情况后,我们可以用类似的思想求解从哪里可以进去以及从哪里可以出去。

不妨先考虑从哪里可以进去,但是这个不大好考虑,于是转而去考虑哪里不可以进去。对于任意一个和左边界相交的圆来讲,如果从这个圆出发,按之前建好的图走能走到上边界的话,那么这个圆的弦,以及弦上面的部分都是不可以进去的。同理,如果从这个圆出发,能走到下边界的话,那么这个圆的弦以及弦下面的部分都是不可以进去的。最后如果从这个圆出发既不能走到上边界,也不能走到下边界,那么就只有这个圆的弦这部分是不可以进去的。这样我就能够处理出来若干个不能进去的位置的区间,根据这些区间就不难找到可能的最下面的可以进去的位置了。

考虑从哪里可以出去是类似的,就不再赘述了。

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#define MAXN 1010
#define MAXM 2000100
const double eps = 1e-;
const double bound = 1000.0;
int N, first[MAXN], e, next[MAXM], v[MAXM], S[], SN;
int dcmp(double x, double y)
{
double t = x - y;
return (t > eps) - (t < -eps);
}
double sqr(double x)
{
return x * x;
}
struct C
{
double x, y, r;
bool insect(C &t)
{
return dcmp(sqr(x - t.x) + sqr(y - t.y), sqr(r + t.r)) <= ;
}
}c[MAXN];
struct Seg
{
double x, y;
bool operator < (const Seg &t) const
{
return x > t.x;
}
}seg[MAXN];
void add(int x, int y)
{
v[e] = y;
next[e] = first[x], first[x] = e ++;
}
void initGraph()
{
memset(first, -, sizeof(first[]) * (N + )), e = ;
for(int i = ; i < ; i ++) S[i] = N + i;
for(int i = ; i < N; i ++)
{
if(dcmp(c[i].y - c[i].r, ) <= ) add(i, S[]), add(S[], i);
if(dcmp(c[i].y + c[i].r, bound) >= ) add(i, S[]), add(S[], i);
for(int j = ; j < N; j ++)
if(i != j && c[i].insect(c[j]))
add(i, j), add(j, i);
}
}
int d[MAXN], q[MAXN];
void bfs(int s)
{
memset(d, , sizeof(d[]) * (N + ));
int rear = ;
d[s] = , q[rear ++] = s;
for(int i = ; i < rear; i ++)
for(int j = first[q[i]]; j != -; j = next[j])
if(!d[v[j]]) d[v[j]] = d[q[i]] + , q[rear ++] = v[j];
}
double find()
{
std::sort(seg, seg + SN);
double pre = bound;
for(int i = ; i < SN; i ++)
{
if(dcmp(seg[i].y, pre) < ) return pre;
pre = std::min(pre, seg[i].x);
}
return pre;
}
void process()
{
initGraph();
bfs(S[]);
if(d[S[]])
{
printf("IMPOSSIBLE\n");
return ;
}
double ans[];
SN = ;
for(int i = ; i < N; i ++)
if(dcmp(c[i].x - c[i].r, ) < )
{
double t = sqrt(sqr(c[i].r) - sqr(c[i].x));
bfs(i);
if(d[S[]]) seg[SN].x = , seg[SN].y = c[i].y + t;
else if(d[S[]]) seg[SN].x = c[i].y - t, seg[SN].y = bound;
else seg[SN].x = c[i].y - t, seg[SN].y = c[i].y + t;
++ SN;
}
ans[] = find();
SN = ;
for(int i = ; i < N; i ++)
if(dcmp(c[i].x + c[i].r, bound) > )
{
double t = sqrt(sqr(c[i].r) - sqr(bound - c[i].x));
bfs(i);
if(d[S[]]) seg[SN].x = , seg[SN].y = c[i].y + t;
else if(d[S[]]) seg[SN].x = c[i].y - t, seg[SN].y = bound;
else seg[SN].x = c[i].y - t, seg[SN].y = c[i].y + t;
++ SN;
}
ans[] = find();
printf("0.00 %.2f 1000.00 %.2f\n", ans[], ans[]);
}
int main()
{
while(scanf("%d", &N) == )
{
for(int i = ; i < N; i ++)
scanf("%lf%lf%lf", &c[i].x, &c[i].y, &c[i].r);
process();
}
return ;
}

 

1568 - Domino Puzzle

domino骨牌连成的是一条链,如果我们把每个点数看成一个点的话,那么这条链就相当于一个只有6个顶点的图上的欧拉道路,再联想欧拉道路的条件,图连通并且至多有2个奇度数的点,那么我们的目标就相当于用最小的代价构造出一个这样的图出来。

如果图本来就连通的话,那么我们只需要在奇度数的点之间连边,使得最后剩余的奇度数的点不超过2个就OK了,而且连的时候先挑标号小的点去连。

如果图本来不连通的话,那么我们就要先使其变成连通图,再像上面那样考虑就可以了,但是我们究竟要连哪些点才能使最终的代价最小呢?如果某个连通块本来就没有奇度数的点怎么办呢?我们先解决第二个问题,挑1个标号最小的点并将其看成2个奇度数的点就OK了。在解决了第二个问题之后,我们就只需要在这些奇数的点之间连边就可以使得图连通并且最多不超过2个奇度数点了。但是这样做就一定能够达到最优的情况吗?当然不能,因为这样想连样例都过不了。。。

接下来就分析一下原因。按前面的做法,实际上就是删掉两个标号最大的奇度数点,剩下的奇度数点的标号之和就是最小代价了,如果这两个标号最大的奇度数点不是一个连通块的,那么自然没什么问题,但如果这两个标号最大的奇度数点是一个连通块的,并且这个连通块只有这两个奇度数点,那该怎么办呢?那么就还要像前面那样挑一个标号最小的点并将其看成2个奇度数点。由于多了这2个新的奇度数点,删掉这两个标号最大的奇度数点就不一定是最优的情况了。因此在求解的过程中我们分情况讨论一下究竟删掉哪2个点。

至此,关键的问题就分析完了,至于如何打印结果就不再赘述了。前面的思路中也并没有证明这样做为什么是最优的,不过真正证明起来也并不困难,所以这里也不再赘述了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
int p[], d[], q[][], qn[], qm[];
bool vis[];
int find(int x)
{
return p[x] == x ? x : (p[x] = find(p[x]));
}
void merge(int x, int y)
{
int tx = find(x), ty = find(y);
if(tx != ty) p[tx] = ty;
}
void makeChoice(int m, int &sv, int &sn)
{
int min = sv + , k;
for(int i = ; i < m; i ++)
{
int t;
if(qn[i] == ) t = sv - q[i][] - q[i][] + * qm[i];
else t = sv - q[i][qn[i] - ] - q[i][qn[i] - ];
if(t < min) min = t, k = i;
} int max1 = -, max2 = -, k1, k2;
for(int i = ; i < m; i ++)
if(q[i][qn[i] - ] > max1)
max1 = q[i][qn[i] - ], k1 = i;
for(int i = ; i < m; i ++)
if(i != k1 && q[i][qn[i] - ] > max2)
max2 = q[i][qn[i] - ], k2 = i; if(sv - max1 - max2 < min)
-- qn[k1], -- qn[k2], sv -= max1 + max2, sn -= ;
else
{
if(qn[k] == ) q[k][] = q[k][] = qm[k], sv = min;
else qn[k] -= , sv = min, sn -= ;
}
}
int main()
{
int n;
while(scanf("%d", &n) == )
{
memset(d, , sizeof(d));
for(int i = ; i <= ; i ++) p[i] = i;
while(n --)
{
int x, y;
scanf("%d%d", &x, &y);
merge(x, y);
++ d[x], ++ d[y];
} memset(vis, , sizeof(vis));
int m = , sn = , sv = ;
for(int i = ; i <= ; i ++)
if(d[i] > && !vis[find(i)])
{
vis[find(i)] = true;
qn[m] = , qm[m] = i;
for(int j = i; j <= ; j ++)
if(find(i) == find(j))
if(d[j] & ) q[m][qn[m] ++] = j, sv += j;
sn += qn[m];
std::sort(q[m], q[m] + qn[m]);
++ m;
} if(m == )
{
if(sn >= ) sv -= q[][sn - ] + q[][sn - ], sn -= ;
printf("%d\n", sv);
printf("%d\n", sn / );
for(int i = ; i < sn; i += )
printf("%d %d\n", q[][i], q[][i + ]);
continue;
} for(int i = ; i < m; i ++)
if(qn[i] == )
q[i][] = q[i][] = qm[i], qn[i] = , sn += , sv += qm[i] * ;
makeChoice(m, sv, sn);
printf("%d\n%d\n", sv, sn / ); int a[], an = , o1, o2;
for(o1 = ; o1 < m && (qn[o1] & ) == ; o1 ++);
for(o2 = o1 + ; o2 < m && (qn[o2] & ) == ; o2 ++);
if(o1 < m) a[an ++] = o1;
for(int i = ; i < m; i ++)
if((qn[i] & ) == ) a[an ++] = i;
if(o2 < m) a[an ++] = o2;
for(int i = ; i < m - ; i ++)
{
int x = a[i], y = a[i + ];
printf("%d %d\n", q[x][-- qn[x]], q[y][-- qn[y]]);
} an = ;
for(int i = ; i < m; i ++)
for(int j = ; j < qn[i]; j ++)
a[an ++] = q[i][j];
for(int i = ; i < an; i += )
printf("%d %d\n", a[i], a[i + ]);
}
return ;
}

1569 - Multiple

为了保证是N的最小正整数倍,那么首先就要保证位数最少,其次要保证最高位尽可能小。而保证位数最少是可以用广搜做出来的,用d[x]表示当前构造的数为模N为x的时候最少需要几位数字,那么依次尝试在x后面添加一位合法的数字y,然后尝试用d[x]+1去更新d[(x*10+y)%N],这样看最后d[0]的值就可以知道是否有解以及最少是几位数了。至于怎样保证打印的结果中最高位尽可能小就不再赘述了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#define INF 0x3f3f3f3f
int N, M, a[], d[], r[], q[];
bool can[];
void print(int x)
{
if(x == ) return ;
for(int i = ; i < M; i ++)
{
int s = (x * + a[i]) % N;
if(can[s] && d[s] == d[x] + )
{
printf("%d", a[i]);
print(s);
break;
}
}
}
int main()
{
while(scanf("%d%d", &N, &M) == )
{
for(int i = ; i < M; i ++) scanf("%d", &a[i]);
if(N == )
{
printf("0\n");
continue;
}
std::sort(a, a + M);
memset(d, 0x3f, sizeof(d[]) * N);
std::vector<int> v[];
int rear = ;
for(int i = ; i < M; i ++)
{
if(a[i] == ) continue;
int x = a[i] % N;
if( < d[x]) d[x] = , q[rear ++] = x;
}
for(int i = ; i < rear; i ++)
{
int x = q[i];
for(int j = ; j < M; j ++)
{
int s = (x * + a[j]) % N;
if(d[x] + < d[s]) d[s] = d[x] + , q[rear ++] = s, v[s].push_back(x);
else if(d[x] + == d[s]) v[s].push_back(x); }
}
if(d[] == INF) printf("");
else
{
memset(can, , sizeof(can[]) * N);
rear = ;
can[] = true, q[rear ++] = ;
for(int i = ; i < rear; i ++)
{
int x = q[i];
for(int j = ; j < v[x].size(); j ++)
{
int y = v[x][j];
if(!can[y]) can[y] = true, q[rear ++] = y;
}
}
for(int i = ; i < M; i ++)
{
if(a[i] == ) continue;
int x = a[i] % N;
if(can[x])
{
printf("%d", a[i]);
print(x);
break;
}
}
}
printf("\n");
}
return ;
}