[中山OI2011]杀人游戏 题解
题目地址:BZOJ:Problem 2438. — [中山市选2011]杀人游 …
May all the beauty be blessed.
题目地址:洛谷:【P4700】[CEOI2011]Traffic – 洛谷、BZOJ:Problem 2387. — [Ceoi2011]Traffic
格丁尼亚的中心位于Kacza河中的一座岛屿。每天清晨,成千上万辆汽车通过岛屿从西岸的住宅区(由桥连接岛的西部)到东岸的工业区(由桥连接岛的东部)。该岛类似于矩形,它的边平行于主方向。故可将它看作是笛卡尔坐标系中的一个A*B的矩形,它的对角分别为(0, 0)和(A, B)。岛上有n个交通节点,编号为1…n(junction,此处可理解为广义的路口),第i个节点坐标为(xi, yi)。如果一个节点的坐标为(0, y),它就位于岛的西岸。类似的,坐标为(A, y)的节点位于岛的东岸。各个节点由街道连接,每条街道用线段连接两个节点。街道有单向行驶或双向行驶之分。除端点外任意两条街道都没有公共点。也没有桥梁或者隧道。你不能对道路网络形状做任何其他假设。沿河岸的街道或节点可能没有入口或者出口街道。由于交通堵塞日趋严重,市长聘请你测试岛上当前的道路网是否足够。要求你写一个程序确定从岛的西岸的每个节点能够到达东岸的多少个节点。
在平面直角坐标系上有 n 个点,其中第 i 个点的坐标是 (xi,yi) ,所有点在一个以 (0,0) 和 (A,B) 为相对顶点的矩形内。
如果 xi=0 ,那么我们称这个点在西侧。如果 xi=A ,那么我们称这个点在东侧。
这些点之间有 m 条边,每条边可能是有向边也可能是无向边,保证边在交点以外的任何地方不相交。
现在请你求出,对于每一个西侧的点,能够沿着边到达多少东侧的点。
输入格式:
第1行包含4个整数n, m, A, B(1≤n≤300000, 0≤m≤900000,1≤A,B≤109),
分别表示格丁尼亚市中心的节点数,街道数和岛屿的尺寸。
接下来的n行,每行包含两个整数xi,yi (0≤xi≤A,0≤yi≤B),表示第i个节点的坐标。任意两个节点的坐标都不相同。
再往下的m行表示街道,每条街道用3个整数ci, di, ki(1≤ci, di≤n, ci≠di, ki∈{1,2}),
表示节点ci、di有街道连接
如果ki=1,表示从ci到di的街道是单向的,否则,这条街道可以双向行驶。每个无序对{ci, di}最多出现1次。
你可以假设西岸节点中至少有1个能够到达东岸的一些节点。
输出格式:
为每个西岸节点输出1行,包括从这个节点出发能够到达东岸的节点数目
输入样例#1:
5 3 1 3 0 0 0 1 0 2 1 0 1 1 1 4 1 1 5 2 3 5 2
输出样例#1:
2 0 2
输入样例#2:
12 13 7 9 0 1 0 3 2 2 5 2 7 1 7 4 7 6 7 7 3 5 0 5 0 9 3 9 1 3 2 3 2 1 3 4 1 4 5 1 5 6 1 9 3 1 9 4 1 9 7 1 9 12 2 10 9 1 11 12 1 12 8 1 12 10 1
输出样例#2:
4 4 0 2
$1 \leq n \leq 300000, 0 \leq m \leq 900000, 1 \leq A, B \leq 10^9$
首先显然可以对这个图缩一波强连通分量。注意到题目隐含了一个平面图的条件,对于一个平面图的左侧点,它能到达的右侧点应该在右侧是相邻的一段。因此,我们可以先扔掉那些不可达的右侧点,然后按照$y$坐标的顺序编号,用DFS来计算出缩点后的图中,每一个点对应的右侧区间左右端点,这样就可以计算出答案了。
复杂度$O(n \log n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
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 MAXN = 300005;
int n, m, A, B;
std::vector<int> gra[MAXN], gran[MAXN];
typedef std::pair<int, int> PII;
std::vector<int> lft, rgt;
PII node[MAXN];
int id[MAXN];
bool vis[MAXN];
int dfn[MAXN], low[MAXN], clk, sno[MAXN], scc, mx[MAXN], mn[MAXN];
bool insta[MAXN];
std::stack<int> sta;
std::set<PII> edges;
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
insta[u] = true; sta.push(u);
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
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;
if(node[p].first == A) {
mx[scc] = std::max(mx[scc], id[p]);
mn[scc] = std::min(mn[scc], id[p]);
}
} while(p != u);
}
}
void dfs(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v);
}
}
void dfs_m(int u) {
if(vis[u]) return;
vis[u] = true;
for(int i = 0; i < gran[u].size(); i++) {
int v = gran[u][i];
dfs_m(v);
mx[u] = std::max(mx[u], mx[v]);
mn[u] = std::min(mn[u], mn[v]);
}
}
inline bool cmp(int a, int b) {
return node[a].second > node[b].second;
}
int main() {
memset(mn, 0x3f, sizeof(mn));
n = readint(); m = readint(); A = readint(); B = readint();
for(int i = 1; i <= n; i++) {
int x = readint(), y = readint();
if(x == 0) lft.push_back(i);
if(x == A) rgt.push_back(i);
node[i] = PII(x, y);
}
for(int i = 1, u, v, k; i <= m; i++) {
u = readint(); v = readint(); k = readint();
gra[u].push_back(v);
if(k == 2) gra[v].push_back(u);
}
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
dfs(u);
}
std::sort(rgt.begin(), rgt.end(), cmp);
for(int i = 0; i < rgt.size(); i++) {
int u = rgt[i];
if(!vis[u]) rgt.erase(rgt.begin() + i), i--;
else id[u] = i + 1;
}
for(int i = 1; i <= n; i++) {
if(!dfn[i]) tarjan(i);
}
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(sno[u] != sno[v] && !edges.count(PII(sno[u], sno[v]))) {
gran[sno[u]].push_back(sno[v]);
edges.insert(PII(sno[u], sno[v]));
}
}
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= scc; i++) dfs_m(i);
std::sort(lft.begin(), lft.end(), cmp);
for(int i = 0; i < lft.size(); i++) {
int u = lft[i];
printf("%d\n", std::max(0, mx[sno[u]] - mn[sno[u]] + 1));
}
return 0;
}
题目地址:洛谷:【P2746】[USACO5.3]校园网Network of Schools – 洛谷
一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。
你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。
输入格式:
输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。
接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。
输出格式:
你的程序应该在输出文件中输出两行。
第一行应该包括一个正整数:子任务 A 的解。
第二行应该包括子任务 B 的解。
输入样例#1:
5 2 4 3 0 4 5 0 0 0 1 0
输出样例#1:
1 2
由于强连通分量内部的点两两互达,不需要考虑内部的情况,直接缩点。对于子任务A,答案就是缩点后入度为0的点的数量,因为当所有入度为0的点都被分发了,入度大于0的点也会被分发,反过来说,如果只分发给入度大于0的点,入度为0的点永远不会被分发到。对于子任务B,考虑只需要从出度为0的点向入度为0的点连边即可,因此答案是出度为0的点的数量和入度为0的点的数量的较大值。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
#include <stack>
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();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 105;
int n, m;
std::vector<int> gra[MAXN];
int dfn[MAXN], low[MAXN], sno[MAXN], scc, clk;
std::stack<int> sta;
bool insta[MAXN];
inline void tarjan(int u) {
dfn[u] = low[u] = ++clk;
insta[u] = true; sta.push(u);
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
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 ind[MAXN], outd[MAXN];
int v;
int main() {
n = readint();
for(int u = 1; u <= n; u++) {
for(;;) {
v = readint(); if(!v) break;
gra[u].push_back(v);
}
}
for(int i = 1; i <= n; i++) {
if(!dfn[i]) tarjan(i);
}
for(int u = 1; u <= n; u++) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(sno[u] != sno[v]) {
outd[sno[u]]++;
ind[sno[v]]++;
}
}
}
if(scc == 1) {
puts("1\n0");
return 0;
}
int out0 = 0, in0 = 0;
for(int i = 1; i <= scc; i++) {
if(!ind[i]) in0++;
if(!outd[i]) out0++;
}
printf("%d\n%d", in0, std::max(out0, in0));
return 0;
}
KSkun
赛题 #A: 负负得正 | 数学,数论,组合数学
赛题 #B: 疯狂的FGOer | 期望DP,二分答案
赛题 #C: Kirara Fantasia的一天 | 图论,Tarjan,缩点,DAGDP
求构建出元素全为1或-1的一个n \times m的矩阵并使得每行每列的乘积都为1或都为-1的方案数。
仅输出0可得25分。
枚举,瞎搞。
优化:我们知道,枚举出n-1行m-1列后剩下的一行一列的值是可以唯一确定的,所以只需要枚举n-1行m-1列的一个矩阵。
总复杂度O(2^{(n-1)(m-1)})。
我们只需要把n-1行m-1列的矩阵填完,剩下一行一列的值可以确定,而且对于任意填法都可以确定。考虑右下角(即第n行第m列)的元素。假设n-1行m-1列矩阵整个的乘积为p,对于最后一列而言,这个位置应当填k^{m-1}p,而对于最后一行而言,这个位置应当填k^{n-1}p。显然当n和m的奇偶性不同且k为-1时,不存在任何一种填法满足要求。其余情况只要填满了n-1行m-1列矩阵都可以成立。因此总方案数是2^{(n-1)(m-1)}。快速幂处理即可。
这个部分分是因为如果你用int存后面的部分分会爆炸,特意设了一档。
总复杂度O(log_2((n-1)(m-1)))。
写long long。
欧拉降幂公式:
a^{k} \equiv a^{k \ mod \ \varphi(m) + \varphi(m)} \ (mod \ m)
继而缩小指数的范围,优化时空复杂度。
// Code by KSkun, 2017/12
#include <cstdio>
const int MO = 1e9 + 7;
long long n, m, k;
inline long long fpow(long long n, long long k) {
long long res = 1;
for(; k; k >>= 1) {
if(k & 1) res = res * n % MO;
n = n * n % MO;
}
return res;
}
int main() {
scanf("%lld%lld%lld", &n, &m, &k);
if(k == -1 && (n & 1) != (m & 1)) printf("0");
else printf("%lld", fpow(2, ((n - 1) % (MO - 1)) * ((m - 1) % (MO - 1)) % (MO - 1) + (MO - 1)));
return 0;
}
CF894B – Ralph And His Magic Field
有一系列任务,每一任务都有一定概率快或慢完成,每个任务完成后可以选择继续完成接下来的任务或是从头开始完成,求一种选择是否从头开始的策略使得完成全部任务的期望值最小。
直接把A_i加起来。
总复杂度O(n)。
出题事故:50%部分分的写法被证实是错误的,因此还没有想到能够用于50%部分分的写法。
原错解:
考虑当我们重置游戏的时候会发生什么。一定是当出现较长时间时重置比较优。如果不重置,每一关的期望应该是上一关期望加上A_i*P_i+B_i*(1-P_i);而如果重置了,则是(exp_{i-1}+B_i)*prob+A_i。此处prob指尝试的次数,可以由题目提示中的方法算出。比较二者大小然后选择即可。但是对于t限制比较严格的数据,由于这里没有剔除超t的可能性,很容易WA。事实证明,这个方法只能过40%的点。由于时间关系,没有对这一层设置部分分,实在抱歉。
考虑对题目模型加以改造。我们假设从头开始进行完游戏的期望是K。这样一种考虑方案需要我们从末尾开始DP计算期望。设计DP状态为:dp[i][j]表示当前计算到第i关,一共用去了j时间,通关的期望。转移:
对于K这个数字,我们发现,它在较大的时候容易满足条件,满足条件的取值是单调的,具有二分性质。考虑二分答案计算K。每计算出一次通关期望,即用其与所设的K比较,若比K小则显然可行。
复杂度O(n^2logn)。
// Code by KSkun, 2017/12
#include <cstdio>
#include <algorithm>
const double EPS = 1e-8;
double dp[55][5005], p[55];
int a[55], b[55], n, t;
inline bool check(double x) {
for(int i = n - 1; i >= 0; i--) {
for(int j = t + 1; j < 5005; j++) {
dp[i + 1][j] = x;
}
for(int j = 0; j <= t; j++) {
dp[i][j] = std::min(x, (dp[i + 1][j + a[i]] + a[i]) * p[i] + (dp[i + 1][j + b[i]] + b[i]) * (1 - p[i]));
}
}
return dp[0][0] < x;
}
int main() {
scanf("%d%d", &n, &t);
for(int i = 0; i < n; i++) {
scanf("%d%d%lf", &a[i], &b[i], &p[i]);
}
double l = 0, r = 1e10, mid;
while(r - l > EPS) {
mid = (l + r) / 2;
if(check(mid)) r = mid; else l = mid;
}
printf("%.2lf", l);
return 0;
}
给一个有向图,边权每次经过时按照一定的规则递减(第一次-1,第二次-2,以此类推),求一条路径使边权最大。
图这么大,显然跑不动。
考虑强连通分量内部的状况,显然是可以跑多几次使得每条边边权全部为0的,因此主要策略是Tarjan缩点+拓扑序DP(用于最长路,你也可以SPFA,没写过)。
如果想把一条边的边权踩完,你需要对这样一个数列求和
\sum_{i=k}^{w} (w-(1+2+ \cdots +i)) = \sum_{i=k}^{w} (w-\frac{i(i+1)}{2})
其中k是使得数列元素为正值的最大正整数。这个数列求和就变成了
\frac{n(n+1)(n+2)}{6}
因此有了代码中cal()
函数的写法。
其他的拓扑序DP找最长路,比较显然,不加以解释。
友情提示:全程用long long
,记得写快读。
复杂度O(n+m)。
// Code by KSkun, 2017/12
#include <cstdio>
#include <cstring>
#include <cmath>
#include <stack>
#include <queue>
#include <algorithm>
typedef long long LL;
const LL inf = 1e15;
struct io {
char buf[1 << 26], *s;
io() {
fread(s = buf, 1, 1 << 26, stdin);
}
inline LL read() {
register LL res = 0;
while(*s < '0' || *s > '9') s++;
while(*s >= '0' && *s <= '9') res = res * 10 + *s++ - '0';
return res;
}
} ip;
#define read ip.read
struct Edge {
int to, nxt;
LL w;
};
struct Graph {
Edge gra[1000005];
int head[1000005], rd[1000005], tot;
LL w[1000005];
Graph() {
memset(head, -1, sizeof head);
tot = 0;
}
inline void addedge(int u, int v, LL w) {
gra[tot].to = v;
gra[tot].w = w;
gra[tot].nxt = head[u];
rd[v]++;
head[u] = tot++;
}
};
int n, m;
Graph g1, g2;
int dfn[1000005], low[1000005], step = 1, sccno[1000005], scctot = 1;
bool instack[1000005];
std::stack<int> sta;
inline void tarjan(int s) {
dfn[s] = low[s] = step++;
instack[s] = true;
sta.push(s);
for(int e = g1.head[s]; e != -1; e = g1.gra[e].nxt) {
int v = g1.gra[e].to;
if(dfn[v] == 0) {
tarjan(v);
low[s] = std::min(low[s], low[v]);
} else if(instack[v]) {
low[s] = std::min(low[s], dfn[v]);
}
}
if(dfn[s] == low[s]) {
LL totw = 0;
while(sta.top() != s) {
sccno[sta.top()] = scctot;
instack[sta.top()] = false;
sta.pop();
}
sccno[sta.top()] = scctot;
instack[sta.top()] = false;
sta.pop();
scctot++;
}
}
inline LL cal(LL w) {
LL n = sqrt(2 * w + 0.25) - 0.5;
return n * w - n * (n + 1) * (n + 2) / 6 + w;
}
inline void calg2() {
for(int u = 1; u <= n; u++) {
for(int e = g1.head[u]; e != -1; e = g1.gra[e].nxt) {
int v = g1.gra[e].to;
if(sccno[u] == sccno[v]) {
g2.w[sccno[u]] += cal(g1.gra[e].w);
} else {
g2.addedge(sccno[u], sccno[v], g1.gra[e].w);
}
}
}
}
std::queue<int> que;
LL ans = 0, dp[1000005];
inline void toposort(int s) {
for(int i = 1; i < scctot; i++) {
dp[i] = -inf;
if(g2.rd[i] == 0) que.push(i);
}
dp[s] = g2.w[s];
while(!que.empty()) {
int u = que.front();
ans = std::max(ans, dp[u]);
que.pop();
for(int e = g2.head[u]; e != -1; e = g2.gra[e].nxt) {
int v = g2.gra[e].to;
g2.rd[v]--;
if(dp[u] != -inf) dp[v] = std::max(dp[v], dp[u] + g2.gra[e].w + g2.w[v]);
if(g2.rd[v] == 0) {
que.push(v);
}
}
}
}
int ut, vt, wt;
int st;
int main() {
n = read();
m = read();
for(int i = 0; i < m; i++) {
ut = read();
vt = read();
wt = read();
g1.addedge(ut, vt, wt);
}
st = read();
for(int i = 1; i <= n; i++) {
if(dfn[i] == 0) tarjan(i);
}
calg2();
toposort(sccno[st]);
printf("%lld", ans);
return 0;
}