A. Layout
题目描述
- 和人类一样,奶牛们在打饭的时候喜欢和朋友站得很近。
- 约翰的编号为(1)到(n)的(nleft ( 2leq nleq 1000 ight ))只奶牛正打算排队打饭。现在请你来安排她们,让她们在数轴上排好队。奶牛的弹性很好,同一个坐标可以站无限只奶牛,排队的顺序必须和她们编号的顺序一致。有(M)对奶牛互相爱慕,她们之间的距离不能超过一定的值,有(K)对奶牛互相敌视,她们的距离不能小于一定的值。
- 那么,首尾奶牛的最大距离是多少呢?
输入格式
- 第一行输入(n,M,K,left ( 0< M,Kleq 5000 ight ))。
- 接下来(M)行每行三个整数(x,y,z),表示编号为(x)和(y)的两头奶牛之间的距离最大不超过(z)。
- 再接下来(K)行每行三个整数(a,b,c),表示编号为(a)和(b)的两头奶牛之间的距离最少为(c)。
输出格式
- 如果没有合理方案,输出(-1),如果首尾两头牛的距离可以无限大,输出(-2),否则输出一个整数表示首尾奶牛的最大距离。
样例输入
4 2 1
1 3 10
2 4 20
2 3 3
样例输出
27
数据范围与提示
- 四只牛分别在(0,7,10,27)。
Solve
- 一道裸的差分约束,没建立超级源点,丢了10分。
- 建立超级源点是考虑到图的一部分不联通,有可能是-1判断成-2
Code
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e3+5, M = 12005;
struct Side {
int t, d, next;
}e[M];
int head[N], tot;
void Add(int x, int y, int z) {
e[++tot] = (Side) {y, z, head[x]};
head[x] = tot;
}
int n, m1, m2, d[N], cnt[N];
bool v[N];
deque<int> q;
int Spfa(int u) {
memset(d, 0x3f, sizeof(d));
memset(v, 0, sizeof(v));
memset(cnt, 0, sizeof(cnt));
while (!q.empty()) q.pop_front();
d[u] = 0;
q.push_back(u); v[u] = 1;
cnt[u] = 1;
while (!q.empty()) {
int x = q.front();
q.pop_front(); v[x] = 0;
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (d[y] > d[x] + e[i].d) {
d[y] = d[x] + e[i].d;
if (v[y]) continue;
v[y] = 1;
if (++cnt[y] > n) return -1;
if (!q.empty() && d[y] <= d[q.front()])
q.push_front(y);
else q.push_back(y);
}
}
}
if (d[n] == 0x3f3f3f3f) return -2;
return d[n];
}
int main() {
scanf("%d%d%d", &n, &m1, &m2);
for (int i = 1; i < n; ++i)
Add(i + 1, i, 0);
while (m1--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (x > y) swap(x, y);
Add(x, y, z);
}
while (m2--) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (x > y) swap(x, y);
Add(y, x, -z);
}
for (int i = 1; i <= n; ++i)
Add(0, i, 0);
if (Spfa(0) == -1) puts("-1");
else printf("%d
", Spfa(1));
return 0;
}
B. 游戏
题目描述
-
Mirko和Slavko爱玩弹球戏。在一个令人激动的星期五,Mirko和Slavko玩了一把弹球游戏。Mirko构建一个有向图,所有顶点最多有1条出边。弹球从1个顶点出发可以沿着一条边移动到它的邻接点,只要它存在,而且它会继续移动到后者的邻接点去,直到最后到达一个找不到出边的顶点才停下来。如果不存在这样的点,弹球可能无限运动下去。
-
为了确信Slavko理解游戏的规则,Mirko将发起一系列询问,询问的类型如下:
- 1 X:除非弹球陷入循环,弹球从X出发,最终将在哪个点停下来。
- 2 X:删除X的出边(保证该边总是存在)
-
注意:询问是按顺序执行的。
输入格式
- 第一行包含一个正整数N(left ( 1leq Nleq 3 imes 10^5 ight )),表示图的定点数。
- 第二行包含由空格隔开N个正整数,第i个数表示从i顶点可以通过出边到达的定点编号。0表示该点没有出边。
- 接下来的一行包含1个整数Q(left ( 1leq Qleq 3 imes 10^5 ight )),表示询问的次数。格式如上所示。
输出格式
- 对于第1类询问,输出弹球停止时所在顶点编号,每行1个,按照查询的顺序输出。如果弹球无法停止,则输出CIKLUS.
样例输入
3
2 3 1
7
1 1
1 2
2 1
1 2
1 1
2 2
1 2
样例输出
CIKLUS
CIKLUS
1
1
2
Solve
- 用并查集解决,删边不好处理,可以倒过来进行加边。
Code
#include <cstdio>
using namespace std;
const int N = 3e5+5;
int n, m, t[N], f[N], od[N], x[N], ans[N], v[N], cnt;
bool c[N];
int found(int x) {
if (v[x] == cnt) return c[x] = 1, f[x] = x;
v[x] = cnt;
return x == f[x] ? x : (f[x] = found(f[x]));
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &t[i]);
f[i] = t[i] ? t[i] : i;
}
scanf("%d", &m);
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &od[i], &x[i]);
if (od[i] == 2) f[x[i]] = x[i];
}
for (int i = m; i; --i)
if (od[i] == 1) {
++cnt;
ans[i] = found(x[i]);
if (c[ans[i]]) ans[i] = -1;
}
else f[x[i]] = t[x[i]];
for (int i = 1; i <= m; ++i)
if (ans[i] == -1) puts("CIKLUS");
else if (ans[i]) printf("%d
", ans[i]);
return 0;
}
C. 数字
题目描述
- 一个数字被称为好数字当他满足下列条件:
- 它有(2 imes n)个数位,n是正整数(允许有前导0)。
- 构成它的每个数字都在给定的数字集合S中。
- 它前n位之和与后n位之和相等或者它奇数位之和与偶数位之和相等。
- 例如对于(n= 2,S= left { 1,2 ight }),合法的好数字有1111,1122,1212,1221,2112,2211,2222这样8种。
- 已知n,求合法的好数字的个数(mod 999983)。
输入格式
- 第一行一个数n。
- 接下来一个长度不超过10的字符串,表示给定的数字集合(不存在重复的数字)。
输出格式
- 一行,一个整数表示合法的好数字的个数(mod 999983)。
样例输入
2
0987654321
样例输出
1240
数据范围与提示
- 对于20%的数据,(nleq 7)。
- 对于100%的数据,(nleq 1000,left | S ight |leq 10) 。
Solve
- 先DP处理出长度为i且所有数字和为j的方案数
(dp[i][j]=sum_{j=0}^{i*Max\_a_i}sum_{k=1}^{len(s) } dp[i-1][j-a[k]]) - 用分步乘法原理可得
(ans=2 imes sum_{i=0}^{n*Max\_a_n} (dp[n][i])^2) - 这样会有重复,根据容斥原理可得
(ans-=sum_{i=0}^{frac {n+1}{2}*Max\_a_n}(dp[frac{n+1}{2}][i])^2-sum_{j=0}^{frac{n}{2}*Max\_a_n}(dp[frac{n}{2}][j])^2) - 具体为什么是减这个数,可以看一下这一段
Code
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1005, M = 999983;
char c[15];
int n, a[15];
long long f[N][N*9], ans, s1, s2;
long long sum(int n) {
long long s = 0;
for (int j = 0; j <= n * 9; ++j)
s += f[n][j] * f[n][j] % M, s %= M;
return s;
}
int main() {
scanf("%d%s", &n, c + 1);
a[0] = strlen(c+1);
for (int i = 1; i <= a[0]; ++i)
a[i] = c[i] - '0';
f[0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= i * 9; ++j)
for (int k = 1; k <= a[0]; ++k)
if (j >= a[k])
f[i][j] += f[i-1][j-a[k]], f[i][j] %= M;
ans = sum(n);
ans = (ans + ans) % M;
s1 = sum(n+1 >> 1);
s2 = sum(n >> 1);
ans -= s1 * s2 % M;
ans = (ans + M) % M;
printf("%lld", ans);
return 0;
}
D. 水站
题目描述
- 已知有一个n层的水站:
- (W_i)表示未操作之前第i层的已有水量;
- (L_i)表示第i个水站能够维持或者储存的水的重量;
- (P_i)表示在第i层进行减压放水操作所需的费用.
- 被压减放水层所储存的所有水都将流向下一层。如果第i层的水量比(L_i)大,则这一层也会(自动)减压(不需要任何费用)。
- 现在想要使最后一层减压(第n级),求最少的花费。这个任务现在交给了你。
输入格式 - 每个输入的第一行包含一个自然数n(left ( 1leq nleq 1.5 imes 10^5 ight ))。
- 接下来n行每行包含3个数(W_i,L_i,P_ileft ( 0leq W_i,L_i,P_ileq 1.5 imes 10^5
ight ))。
输出格式 - 第一行输出所需的最小费用
- 第二行若干个整数,从小到大输出必须减压的层的编号。
样例输入
3
1000 1000 1
0 1000 2
2 10 100
样例输出
3
1 2
数据范围与提示
- 给第一层和第二层减压
- (30%:nleq 5000)
- (100%:nleq 1.5 imes 10^5)
Solve
- 我们首先要知道最优解一定是从一个点开始到n,中途没有断掉,根据这可以写出(O(n^2))的暴力,原题就可以过了。
- 当数据加强到1e5的时候就得考虑别的方法,可以考虑二分,对于每层来说,从上面找到一个最高的一层,使得总水量不会让这层溢出,这样这层就会对这些的价值产生影响,在用差分数组进行优化,时间复杂度为(O(n log n))
Code
#include <cstdio>
#include <vector>
using namespace std;
const int N = 15e4+5;
int n, w[N], m[N], p[N], s[N], c[N], ans = 1<<30, h;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &w[i], &m[i], &p[i]),
s[i] = s[i-1] + w[i];
for (int i = 1; i <= n; ++i) {
int l = 1, r = i + 1;
while (l < r) {
int mid = l+r >> 1;
if (s[i] - s[mid-1] <= m[i]) r = mid;
else l = mid + 1;
}
c[r] += p[i];
c[i+1] -= p[i];
}
for (int i = 1; i <= n; ++i) {
c[i] += c[i-1];
if (ans > c[i]) ans = c[i], h = i;
}
printf("%d
", ans);
for (int i = h; i <= n; i++)
if (s[i] - s[h-1] <= m[i])
printf("%d ", i);
return 0;
}