[TJOI2015]弦论 题解
题目地址:ė …
May all the beauty be blessed.
题目地址:洛谷:【P3825】[NOI2017]游戏 – 洛谷、BZOJ:Problem 4945. — [Noi2017]游戏
狂野飙车是小 L 最喜欢的游戏。与其他业余玩家不同的是,小 L 在玩游戏之余,还精于研究游戏的设计,因此他有着与众不同的游戏策略。
小 L 计划进行 n 场游戏,每场游戏使用一张地图,小 L 会选择一辆车在该地图上完成游戏。
小 L 的赛车有三辆,分别用大写字母A、B、C表示。地图一共有四种,分别用小写字母x、a、b、c表示。其中,赛车A不适合在地图a上使用,赛车B不适合在地图b上使用,赛车C不适合在地图c上使用,而地图x则适合所有赛车参加。适合所有赛车参加的地图并不多见,最多只会有d张。
n 场游戏的地图可以用一个小写字母组成的字符串描述。例如:S=xaabxcbc表示小 L 计划进行 8 场游戏,其中第 1 场和第 5 场的地图类型是x,适合所有赛车,第 2 场和第 3 场的地图是a,不适合赛车A,第 4 场和第 7 场的地图是b,不适合赛车B,第 6 场和第 8 场的地图是c,不适合赛车C。
小 L 对游戏有一些特殊的要求,这些要求可以用四元组 (i,hi,j,hj) 来描述,表示若在第 i 场使用型号为 hi 的车子,则第 j 场游戏要使用型号为 hj 的车子。
你能帮小 L 选择每场游戏使用的赛车吗?如果有多种方案,输出任意一种方案。如果无解,输出 “-1’’(不含双引号)。
你正在玩一个赛车游戏,一共有3种车型,每一场有不适合的车型,有的比赛没有不适合的车型,这种比赛的数量$\leq 8$。有一些限制条件,表示某场比赛如果使用某种车型,那么另一场比赛必须使用另种车型。求是否存在每场比赛采用的车型的安排符合上述所有条件。
输入格式:
输入第一行包含两个非负整数 n,d 。
输入第二行为一个字符串 S 。 n,d,S 的含义见题目描述,其中 S 包含 n 个字符,且其中恰好 d 个为小写字母 x 。
输入第三行为一个正整数 m ,表示有 m 条用车规则。接下来 m 行,每行包含一个四元组 i,hi,j,hj ,其中 i,j 为整数, hi,hj 为字符a、b或c,含义见题目描述。
输出格式:
输出一行。
若无解输出 “-1’’(不含双引号)。
若有解,则包含一个长度为 n 的仅包含大写字母A、B、C的字符串,表示小 L 在这 n 场游戏中如何安排赛车的使用。如果存在多组解,输出其中任意一组即可。
输入样例#1:
3 1 xcc 1 1 A 2 B
输出样例#1:
ABA
本题可以使用2-SAT解决。
如果不存在可以使用三种的游戏,那么我们按如下方式建图。
跑完2-SAT输出方案就好。对于可以使用三种车型的游戏,则枚举它不能使用某种车型,就转变为类似普通游戏的模型,枚举到一个方案判断一次即可。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline char readsingle() {
char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 100005;
int n, m;
char s[MAXN], s1[MAXN];
struct Info {
int i, j;
char hi, hj;
} infos[MAXN];
struct Edge {
int to, nxt;
} gra[MAXN << 1], gran[MAXN << 1];
int head[MAXN], headn[MAXN], tot, totn;
inline void addedge(int u, int v) {
gra[tot] = Edge {v, head[u]}; head[u] = tot++;
}
inline void addedgen(int u, int v) {
gran[totn] = Edge {v, headn[u]}; headn[u] = totn++;
}
inline void reset() {
tot = totn = 0;
memset(head, -1, sizeof(head));
memset(headn, -1, sizeof(headn));
}
int dfn[MAXN], low[MAXN], clk, sno[MAXN], scc, op[MAXN];
std::stack<int> sta;
bool insta[MAXN];
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
sta.push(u); insta[u] = true;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if(insta[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]) {
scc++;
int p;
do {
p = sta.top(); sta.pop();
insta[p] = false;
sno[p] = scc;
} while(p != u);
}
}
int deg[MAXN];
std::queue<int> que;
int mark[MAXN];
void dfs_mark(int u) {
if(mark[u]) return;
mark[u] = -1;
for(int i = headn[u]; ~i; i = gran[i].nxt) {
int v = gran[i].to;
dfs_mark(v);
}
}
inline void toposort() {
for(int i = 1; i <= scc; i++) {
if(!deg[i]) que.push(i);
}
while(!que.empty()) {
int u = que.front(); que.pop();
if(mark[u]) continue;
mark[u] = 1;
dfs_mark(op[u]);
for(int i = headn[u]; ~i; i = gran[i].nxt) {
int v = gran[i].to;
if(!--deg[v]) que.push(v);
}
}
}
inline int id(int x, char y) {
if(s1[x] == 'a') {
if(y == 'B') return x;
else return x + n;
} else {
if(y == 'A') return x;
else return x + n;
}
}
inline char choice(int x) {
int xx = x > n ? x - n : x;
if(s1[xx] != 'a' && id(xx, 'A') == x) return 'A';
else if(s1[xx] != 'b' && id(xx, 'B') == x) return 'B';
else if(s1[xx] != 'c' && id(xx, 'C') == x) return 'C';
}
inline int oppo(int x) {
if(x > n) return x - n;
else return x + n;
}
inline bool work() {
reset();
for(int i = 1; i <= m; i++) {
Info x = infos[i];
if(s1[x.i] == tolower(x.hi)) {
continue;
} else if(s1[x.j] == tolower(x.hj)) {
addedge(id(x.i, x.hi), oppo(id(x.i, x.hi)));
} else {
addedge(id(x.i, x.hi), id(x.j, x.hj));
addedge(oppo(id(x.j, x.hj)), oppo(id(x.i, x.hi)));
}
}
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
for(int i = 1; i <= n * 2; i++) {
if(!dfn[i]) tarjan(i);
}
for(int i = 1; i <= n; i++) {
if(sno[i] == sno[i + n]) return false;
op[sno[i]] = sno[i + n];
op[sno[i + n]] = sno[i];
}
memset(deg, 0, sizeof(deg));
for(int u = 1; u <= n * 2; u++) {
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(sno[u] != sno[v]) {
addedgen(sno[v], sno[u]);
deg[sno[u]]++;
}
}
}
toposort();
for(int i = 1; i <= n; i++) {
if(mark[sno[i]] == 1) putchar(choice(i));
else putchar(choice(i + n));
}
return true;
}
bool dfs_s(int step) {
if(step > n) return work();
if(s[step] != 'x') {
s1[step] = s[step];
if(dfs_s(step + 1)) return true;
} else {
s1[step] = 'a';
if(dfs_s(step + 1)) return true;
s1[step] = 'b';
if(dfs_s(step + 1)) return true;
s1[step] = 'c';
if(dfs_s(step + 1)) return true;
}
}
int main() {
n = readint(); readint();
for(int i = 1; i <= n; i++) {
s[i] = readsingle();
}
m = readint();
for(int i = 1; i <= m; i++) {
infos[i].i = readint();
infos[i].hi = readsingle();
infos[i].j = readint();
infos[i].hj = readsingle();
}
if(!dfs_s(1)) puts("-1");
return 0;
}
题目地址:BZOJ:Problem 3590. — [Snoi2013]Quare
4.20四川芦山地震发生后,抗震救灾委员会接到一个紧急任务,四川省给该委员会发了一份地图,这份地图给出了该省一些城市的情况:任两个城市是用一条或多条公路连接起来的,也可以没有公路连接,但是每个城市都可以直接或间接地到达另外的城市,注意这些公路是可以双向行驶的。由于最近余震、暴雨造成泥石流倾泻,使得车辆在这些公路上行驶很不安全,于是四川省决定尽快对部分公路进行抢修,以保障救援车辆行车安全。
该省对所有的公路情况都进行了勘察,分析估计了抢修某段公路所需要花费的时间,并记录在地图中。现在该省希望抗震救灾委员会能找到一个方案,该方案决定出哪些公路需要抢修,使得抢修后的公路仍能保证任意两个城市之间都能直接或间接地相连,同时为了安全起见,即使某一条抢修的公路被泥石流阻断了,任意两城市仍能保持这个性质。由于时间紧迫,抗震救灾委员会还需保证找到的这个方案总抢修时间最短。
给你一个连通无向图,求这个图的一个包含所有点的最小权边双连通子图。
输入格式:
输入文件有多组数据,第1行为 1 个整数t,为 case总数,接下来按顺序给出每个case 描述,首先是两个整数 n,m(1≤n≤12, 1≤m≤40)分别表示城市数量和公路数量,下面m行每行 3个整数x,y,c 描述了一条公路的情况:x城市与y城市之间的一条公路,抢修该公路需要 c个单位时间。
注意上面所说的两城市间可能有多条公路。
输出格式:
按顺序输出每个 case 的结果,如果找不到一条合适的方案,则输出一行“impossible”,否则输出一个整数,为抢修的最优方案所需要的总时间。
输入样例#1:
2 4 6 1 2 1 1 3 2 1 3 3 2 4 2 3 4 1 2 3 1 2 1 1 2 3
输出样例#1:
6 impossible
参考资料:BZOJ3590【状压DP】 – CSDN博客
看到了数据范围就想到状压,但是DP并不好做。
我们想到一个边双连通分量可以从一个小的子分量加上一条链扩展而来,即把这条链通过端点的出边连接到子分量上。
我们考虑处理出$g[S][u][v]$表示状态$S$中的所有点构成一条链,端点分别是$u$和$v$的最小权值和。显然只有一个点的链权值为0,只有一条边的链权值为边权,这些可以作为DP初值。然后枚举$u$、$v$和一条$v$的出边,把出边连接的点并入链中这样转移就好。
还要处理出$h[S][u][0/1]$表示$u$到状态$S$($u \notin S$)中的任意一点的最短距离和次短距离。对于每一个$S$,枚举一个不在里面的点$u$然后枚举出边求最小值即可。
有了上面的信息,我们可以开始计算答案了,$f[S]$表示$S$内的点构成一个点双连通分量的最小权值和。我们每次往$S$中加入一条链,如果这条链是一个点,那么要加上这个点到$S$的最短和次短距离,否则加上链两个端点到$S$的最短距离和链权和。
也就是说,这个题本身是一个大力DP题。复杂度比较难看。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int INF = 0x10101010;
int T, n, m;
struct Edge {
int to, w;
};
std::vector<Edge> gra[15];
int bin[15], cnt[1 << 12], f[1 << 12], g[1 << 12][15][15], h[1 << 12][15][2];
int main() {
T = readint();
for(int i = 1; i < 15; i++) bin[i] = 1 << (i - 1);
for(int i = 1; i < (1 << 12); i++) cnt[i] = cnt[i >> 1] + (i & 1);
while(T--) {
n = readint(); m = readint();
for(int i = 1; i <= n; i++) gra[i].clear();
for(int i = 1, u, v, w; i <= m; i++) {
u = readint(); v = readint(); w = readint();
gra[u].push_back(Edge {v, w});
gra[v].push_back(Edge {u, w});
}
memset(g, 0x10, sizeof(g));
for(int i = 1; i <= n; i++) {
g[bin[i]][i][i] = 0;
}
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i].to;
g[bin[u] | bin[v]][u][v] = std::min(g[bin[u] | bin[v]][u][v], gra[u][i].w);
}
}
for(int i = 1; i < (1 << n); i++) {
for(int u = 1; u <= n; u++) {
for(int v = 1; v <= n; v++) {
if(!(i & bin[u]) || !(i & bin[v])) continue;
for(int j = 0; j < gra[v].size(); j++) {
int w = gra[v][j].to;
if(i & bin[w]) continue;
g[i | bin[w]][u][w] =
std::min(g[i | bin[w]][u][w], g[i][u][v] + gra[v][j].w);
}
}
}
}
memset(h, 0x10, sizeof(h));
for(int i = 1; i < (1 << n); i++) {
for(int u = 1; u <= n; u++) {
if(i & bin[u]) continue;
for(int j = 0; j < gra[u].size(); j++) {
int v = gra[u][j].to;
if(!(i & bin[v])) continue;
if(gra[u][j].w <= h[i][u][0]) {
h[i][u][1] = h[i][u][0];
h[i][u][0] = gra[u][j].w;
} else if(gra[u][j].w < h[i][u][1]) {
h[i][u][1] = gra[u][j].w;
}
}
}
}
memset(f, 0x10, sizeof(f));
for(int i = 1; i <= n; i++) {
f[bin[i]] = 0;
}
for(int i = 1; i < (1 << n); i++) {
if(cnt[i] < 2) continue;
for(int s = (i - 1) & i; s; s = (s - 1) & i) {
int t = i - s;
for(int u = 1; u <= n; u++) {
for(int v = 1; v <= n; v++) {
if((s & bin[u]) && (s & bin[v])) {
if(u == v) {
f[i] = std::min(f[i], f[t] + g[s][u][u] + h[t][u][0] + h[t][u][1]);
} else {
f[i] = std::min(f[i], f[t] + g[s][u][v] + h[t][u][0] + h[t][v][0]);
}
}
}
}
}
}
if(f[(1 << n) - 1] >= INF) {
puts("impossible");
} else {
printf("%d\n", f[(1 << n) - 1]);
}
}
return 0;
}
题目地址:洛谷:【P2805】[NOI2009]植物大战僵尸 – 洛谷、BZOJ:Problem 1565. — [NOI2009]植物大战僵尸
Plants vs. Zombies(PVZ)是最近十分风靡的一款小游戏。Plants(植物)和Zombies(僵尸)是游戏的主角,其中Plants防守,而Zombies进攻。该款游戏包含多种不同的挑战系列,比如Protect Your Brain、Bowling等等。其中最为经典的,莫过于玩家通过控制Plants来防守Zombies的进攻,或者相反地由玩家通过控制Zombies对Plants发起进攻。
现在,我们将要考虑的问题是游戏中Zombies对Plants的进攻,请注意,本题中规则与实际游戏有所不同。游戏中有两种角色,Plants和Zombies,每个Plant有一个攻击位置集合,它可以对这些位置进行保护;而Zombie进攻植物的方式是走到植物所在的位置上并将其吃掉。
游戏的地图可以抽象为一个N行M列的矩阵,行从上到下用0到N–1编号,列从左到右用0到M–1编号;在地图的每个位置上都放有一个Plant,为简单起见,我们把位于第r行第c列的植物记为Pr, c。
Plants分很多种,有攻击类、防守类和经济类等等。为了简单的描述每个Plant,定义Score和Attack如下:
Score[Pr, c]
Zombie击溃植物Pr, c可获得的能源。若Score[Pr, c]为非负整数,则表示击溃植物Pr, c可获得能源Score[Pr, c],若为负数表示击溃Pr, c需要付出能源 -Score[Pr, c]。
Attack[Pr, c]
植物Pr, c能够对Zombie进行攻击的位置集合。
Zombies必须从地图的右侧进入,且只能沿着水平方向进行移动。Zombies攻击植物的唯一方式就是走到该植物所在的位置并将植物吃掉。因此Zombies的进攻总是从地图的右侧开始。也就是说,对于第r行的进攻,Zombies必须首先攻击Pr, M-1;若需要对Pr, c(0≤c<M-1)攻击,必须将Pr,M-1, Pr, M-2 … Pr, c+1先击溃,并移动到位置(r, c)才可进行攻击。
在本题的设定中,Plants的攻击力是无穷大的,一旦Zombie进入某个Plant的攻击位置,该Zombie会被瞬间消灭,而该Zombie没有时间进行任何攻击操作。因此,即便Zombie进入了一个Plant所在的位置,但该位置属于其他植物的攻击位置集合,则Zombie会被瞬间消灭而所在位置的植物则安然无恙(在我们的设定中,Plant的攻击位置不包含自身所在位置,否则你就不可能击溃它了)。
Zombies的目标是对Plants的阵地发起进攻并获得最大的能源收入。每一次,你可以选择一个可进攻的植物进行攻击。本题的目标为,制定一套Zombies的进攻方案,选择进攻哪些植物以及进攻的顺序,从而获得最大的能源收入。
你在玩植物大战僵尸游戏,你操控僵尸破坏植物,破坏一棵植物要么付出代价要么获得收益,分别以正负权体现。植物的攻击力无穷,也就是说,当僵尸刚进入它能攻击的格子中时会被瞬间干掉;僵尸的攻击力也无穷,也就是说它没被干掉的时候能瞬间干掉一棵植物。求一种进攻方法,使得获得的权值最大。
输入格式:
输入文件pvz.in的第一行包含两个整数N, M,分别表示地图的行数和列数。
接下来N×M行描述每个位置上植物的信息。第r×M + c + 1行按照如下格式给出植物Pr, c的信息:第一个整数为Score[Pr, c], 第二个整数为集合Attack[Pr, c]中的位置个数w,接下来w个位置信息(r’, c’),表示Pr, c可以攻击位置第r’ 行第c’ 列。
输出格式:
输出文件pvz.out仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。
输入样例#1:
3 2 10 0 20 0 -10 0 -5 1 0 0 100 1 2 1 100 0
输出样例#1:
25
约20%的数据满足1 ≤ N, M ≤ 5;
约40%的数据满足1 ≤ N, M ≤ 10;
约100%的数据满足1 ≤ N ≤ 20,1 ≤ M ≤ 30,-10000 ≤ Score ≤ 10000
首先,这是一个最大权闭合子图的模型,即,建立源→负权点,正权点→汇,保护点→被保护点的网络,其中源汇连边边权为点权绝对值,保护关系边权无限。这样,正权和-最小割就是答案。但是如果正负权点和源汇的建边关系反过来了会WA,这是由于一个正权点保护一个负权点显然是不优的,这些边的存在没有意义,更应该让负权点→正权点的边参与到流中。
信心满满的你写了一个Dinic,然后发现不对劲,这个图上有环!注意到本题提供了一个隐藏条件,即一个植物也可以保护它后面的植物,如果它后面的一个植物也保护了它,那它就无敌了。我们拓扑找环,然后把环上的点连同这些点可以到达的点全删了,然后把剩下的图当网络跑最大流就是正解。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <queue>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
return res * neg;
}
const int MAXN = 605, INF = 1e9;
int n, m;
struct Edge {
int to, cap, nxt;
} gra[1000005], gra2[1000005];
int head[MAXN], head2[MAXN], deg[MAXN], tot, tot2;
inline void addedge(int u, int v, int cap) {
gra[tot] = Edge {v, cap, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, 0, head[v]}; head[v] = tot++;
}
inline void addedge2(int u, int v) {
gra2[tot2] = Edge {v, 0, head2[u]}; head2[u] = tot2++;
deg[v]++;
}
bool del[MAXN];
void dfs_del(int u) {
del[u] = true;
for(int i = head2[u]; ~i; i = gra2[i].nxt) {
int v = gra2[i].to;
if(!del[v]) dfs_del(v);
}
}
inline void toposort() {
std::queue<int> que;
for(int i = 1; i <= n * m; i++) {
if(!deg[i]) que.push(i);
else del[i] = true;
}
while(!que.empty()) {
int u = que.front(); que.pop(); del[u] = false;
for(int i = head2[u]; ~i; i = gra2[i].nxt) {
int v = gra2[i].to;
if(!--deg[v]) que.push(v);
}
}
for(int i = 1; i <= n * m; i++) {
if(del[i]) dfs_del(i);
}
}
int level[MAXN];
inline bool bfs(int s, int t) {
memset(level, -1, sizeof(level));
std::queue<int> que;
que.push(s); level[s] = 1;
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(level[v] == -1 && gra[i].cap) {
level[v] = level[u] + 1;
if(v == t) return true;
que.push(v);
}
}
}
return level[t] != -1;
}
int cur[MAXN];
int dfs(int u, int t, int left) {
if(u == t || !left) return left;
int flow = 0;
for(int &i = cur[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(level[v] == level[u] + 1 && gra[i].cap) {
int d = dfs(v, t, std::min(left, gra[i].cap));
if(d) {
gra[i].cap -= d; gra[i ^ 1].cap += d;
flow += d; left -= d;
if(!left) break;
}
}
}
return flow;
}
inline int dinic(int s, int t) {
int flow = 0;
while(bfs(s, t)) {
memcpy(cur, head, sizeof(head));
int f;
while(f = dfs(s, t, INF)) flow += f;
}
return flow;
}
inline int id(int x, int y) {
return (x - 1) * m + y;
}
int w[MAXN], S, T;
int main() {
memset(head, -1, sizeof(head));
memset(head2, -1, sizeof(head2));
n = readint(); m = readint(); S = n * m + 1; T = S + 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
w[id(i, j)] = readint();
int t = readint();
while(t--) {
int x = readint() + 1, y = readint() + 1;
addedge2(id(i, j), id(x, y));
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 2; j <= m; j++) {
addedge2(id(i, j), id(i, j - 1));
}
}
toposort();
int sum = 0;
for(int i = 1; i <= n * m; i++) {
if(del[i]) continue;
if(w[i] > 0) {
sum += w[i]; addedge(i, T, w[i]);
} else {
addedge(S, i, -w[i]);
}
for(int j = head2[i]; ~j; j = gra2[j].nxt) {
int v = gra2[j].to;
addedge(i, v, INF);
}
}
printf("%d", sum - dinic(S, T));
return 0;
}