总体来说,Day1的3题非常水,Day2的难度却飙升到一定境界了……然后我就GG了……
T1 铺设道路
题目链接
这道题一眼原题,显然,如果
,那么就会对答案造成
的贡献,否则无贡献。于是代码只有十行。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, t, lst = 0, res = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &t);
if (t > lst) res += t - lst;
lst = t;
}
printf("%d\n", res);
return 0;
}
T2 货币系统
题目链接
这道题在考场上看见的时候有点慌,毕竟刚开始一点思路都没有……后来猜测了一个很有道理的结论,最小选出的集合
一定是原集合
的子集。
证明:
反证法,如果不满足,必然有至少一个数
,并且
不能被
中的其他数表示,并且
一定可以被
中的数表示(否则就多出了一个可以表示的数,不符合题意)。
考虑
中可以表示
且不存在于
中的数的集合为
,如果
中的所有数字都可以被
表示,那么
一定也可以被
表示,矛盾;否则
不能表示出
中的某个数字,不符合题意。
综上所述,选出集合一定是原集合的子集。
然后这道题就很简单了,
要求能够表示出
中的所有数字,也就是说如果
中某个数字可以被其它数字表示就删掉,剩下的必须保留。这个东西把
从小到大排个序跑一遍完全背包就行了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105, maxm = 25005;
int f[maxm], arr[maxn], n, T;
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", arr + i);
sort(arr + 1, arr + 1 + n);
memset(f, 0, sizeof(f));
f[0] = 1;
int res = 0;
for (int i = 1; i <= n; i++) {
if (f[arr[i]]) continue;
res++;
for (int j = arr[i]; j < maxm; j++)
f[j] |= f[j - arr[i]];
}
printf("%d\n", res);
}
return 0;
}
T3 赛道修建
题目链接
显然,题目要求最小化长度最长路径的长度,肯定可以二分。之后就在于如何判定。假设当前二分的值为
,我们对于树进行一遍dfs,每个子树尽量多地选路径,选出的个数一样多的情况下要求留给连到子树根的没用过的链最长。然后对于每个节点,它会挂着很多没有用过的链,这个可以贪心选取链两两接起来(具体操作后面再说),并返回剩下的最长链。为什么这样是对的?如果子树没有尽量多地选路径,但是可以返回一个更长的链怎么办?显然这个不够优秀。因为原来的链大不了就不选,答案最多减少1,而后面没有尽量多地选路径已经让答案至少减少了1,因此贪心是正确的。
我们接下来考虑怎么把链两两配对还能够返回剩下链的最大值。我们把链从小到大排序,对于每一条链,我们去找最短的链,使得这两条链的长度之和
,然后删除这两条链继续寻找。我在考场上怕set被卡常,所以用链表实现的,类似two pointer的技巧排序之后扫一遍就行了(考场上怕出锅写了很多奇怪的东西,其实估计很多都不需要)。
然后这道题就在
的复杂度内解决了,我相信CCF i7的CPU一定可以跑过去的!心中有理想,国家能富强
(好吧这份代码在洛谷上最慢的点40ms)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> P;
const int maxn = 50005;
struct Edge { int to, val, next; } edge[maxn * 2];
int par[maxn], arr[maxn], ord[maxn], head[maxn];
int used[maxn], tot, n, m, lim;
P f[maxn];
void addedge(int u, int v, int w) {
edge[++tot] = (Edge) { v, w, head[u] };
head[u] = tot;
}
void efs(int u, int fa) {
par[ord[++tot] = u] = fa;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != fa) efs(v, u);
}
}
int rgt[maxn], lft[maxn];
void del(int x) {
lft[rgt[x]] = lft[x];
rgt[lft[x]] = rgt[x];
used[x] = 1;
}
int get_rgt(int x) {
while (used[rgt[x]]) x = rgt[x];
return rgt[x];
}
int get_lft(int x) {
while (used[lft[x]]) x = lft[x];
return lft[x];
}
int check(int mid) {
memset(used, 0, sizeof(used));
lim = mid;
for (int i = tot; i > 0; i--) {
int u = ord[i];
int cnt = 0, nn = 0;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to, w = edge[i].val;
if (v == par[u]) continue;
P p = f[v];
cnt += p.first;
arr[++nn] = p.second + w;
}
sort(arr + 1, arr + 1 + nn);
arr[nn + 1] = 0;
for (int i = 1; i <= nn; i++)
rgt[i] = i + 1, lft[i] = i - 1;
rgt[0] = 1, lft[nn + 1] = nn;
for (int i = 0; i <= nn + 1; i++) used[i] = 0;
for (int i = nn; i > 0; i--) if (arr[i] >= lim) del(i), ++cnt;
for (int i = rgt[0], j = lft[nn + 1]; i < nn && rgt[i] <= nn;) {
int t = arr[i];
if (j <= i) j = rgt[i];
while (lft[j] > i && arr[lft[j]] + t >= lim) j = lft[j];
if (arr[j] + t < lim) { i = rgt[i]; continue; }
del(i), del(j), ++cnt;
i = get_rgt(i), j = get_rgt(j);
}
f[u] = P(cnt, arr[lft[nn + 1]]);
}
return f[1].first >= m;
}
int main() {
scanf("%d%d", &n, &m);
int sum = 0;
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
sum += w;
}
efs(1, tot = 0);
int l = 0, r = sum / m + 10;
while (l + 1 < r) {
int mid = (l + r) >> 1;
if (check(mid)) l = mid;
else r = mid;
}
printf("%d\n", l);
fclose(stdin), fclose(stdout);
return 0;
}
T4 旅行
题目链接
本来是一道水题,我先开始没看到
的条件,后来又以为数据量1E5,白白浪费了20min……
树肯定很简单,每次贪心地往最小的儿子走即可;奇环树就暴力剖环,然后当成树跑一遍取最小值就行了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005, maxm = 10005;
vector<int> gra[maxn];
int head[maxn], vis[maxn], sta[maxn], isc[maxn], tot, n, m, ca, cb;
bool findc(int u, int fa) {
vis[sta[++tot] = u] = 1;
int s = gra[u].size();
for (int i = 0; i < s; i++) {
int v = gra[u][i];
if (v == fa) continue;
if (vis[v]) {
int x = 0;
while (x != v) isc[x = sta[tot--]] = 1;
return true;
}
if (findc(v, u)) return true;
}
--tot;
return false;
}
int temp[maxn], ans[maxn];
void dfs(int u, int fa) {
temp[++tot] = u;
int s = gra[u].size();
for (int i = 0; i < s; i++) {
int v = gra[u][i];
if (v == fa || (u == ca && v == cb) || (u == cb && v == ca)) continue;
dfs(v, u);
}
}
void solve() {
dfs(1, tot = 0);
int flag = 1;
for (int i = 1; i <= n; i++) if (temp[i] != ans[i])
{ if (temp[i] > ans[i]) flag = 0; break; }
if (flag) memcpy(ans, temp, sizeof(temp));
}
int main() {
scanf("%d%d", &n, &m);
memset(ans, 0x3f, sizeof(ans));
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
gra[u].push_back(v);
gra[v].push_back(u);
}
for (int i = 1; i <= n; i++)
sort(gra[i].begin(), gra[i].end());
if (n == m + 1) {
solve();
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}
findc(1, tot = 0);
for (int i = 1; i <= n; i++) if (isc[i]) {
int s = gra[i].size();
for (int j = 0; j < s; j++) {
int v = gra[i][j];
if (isc[v] && v > i)
ca = i, cb = v, solve();
}
}
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}
T5 填数游戏
题目链接
好吧我刚开始看到这道题
那么小,肯定是什么状压dp,以至于想到做法之后以为不是正解,再加上巨难写,考场上就放弃了GG……最后只剩了20min给T3。
当然这道题可以打表,但我觉得这也太tm没意思了吧(主要是CCF脑子抽,明明
都可以出到10的一百万次方的题非要出成一个打表题来迷惑人)。
第一,行数为
,列数为
的矩阵方案数量等价于行数为
,列数为
的矩阵方案数量。这个把原矩阵转置一下每个数取反即可得到新矩阵中的方案。因此它们一一对应。
剩下来的其实思路很简单,满足条件的矩形有两个性质:
1.每个数小于等于它右上角的数(即每条从右上到左下的斜线上的数字单调不降)。
2.如果一个数字等于它右上角的数字,那么他们右下角的矩形是条纹状的(即右下角的子矩形每条从右上到左下的斜线上的数字相等)。
可以证明,上面两个条件是矩形合法的充要条件。于是我们考虑分类讨论(记矩形为
):
1.
,这样有两种情况,全为0和全为1.右下角的矩形是条纹状的,对答案的贡献为
.
2.
,我们考虑第三条斜线上的数值。这个时候就要特判
的情况了。不过这些情况用上面的结论还是挺好算的。剩下的情况第三条斜线可能是000,001,011,111四种情况,000,111的情况其实和上面的那个差不多,贡献是
。
否则,我们不妨设
,先考虑110的情况。我们可以枚举另外一个限制出现在什么位置。这个玩意儿对答案的贡献首先有
这个其实是等比数列求和,可以快速幂计算。剩下的分两种讨论(包含了没有剩余限制的情况)。
2.1
。剩余贡献为
2.2
。剩余贡献为
,这也是等比数列,快速幂计算。
对于001的情况,和上述情况很相似了,而且讨论的部分更少。于是答案就可以在
的时间内计算完毕。
嘤嘤嘤考场上被数据量坑了啊!!!!
(代码有很多地方没优化,就是按照上面的算式直接打的……)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1000000007;
ll modpow(ll a, int b) {
ll res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
if (n > m) swap(n, m);
if (n == 1) return printf("%lld\n", modpow(2, m)) * 0;
if (n == 2) return printf("%lld\n", modpow(3, m - 1) * 4 % mod) * 0;
ll res = 2LL * modpow(4, n - 2) * modpow(3, m - n) % mod * modpow(2, n - 1) % mod;
if (n == 3) {
if (m == 3) {
res += 12 + 6 + 6;
return printf("%lld\n", res * 2 % mod) * 0;
}
res = (res + 32LL * modpow(3, m - 4)) % mod;
res = (res + 48LL * (modpow(3, m - 4) - 1) % mod * (mod + 1) / 2 + 24) % mod;
res = (res + 16LL * modpow(3, m - 4)) % mod;
return printf("%lld\n", res * 2 % mod) * 0;
}
res = (res + 10LL * modpow(4, n - 4) % mod * modpow(3, m - n) % mod * modpow(2, n - 1)) % mod;
res = (res + 20LL * modpow(3, m - n) % mod * modpow(2, n - 1) % mod * (modpow(4, n - 4) - 1) % mod * (mod + 1) / 3) % mod;
if (n == m) res = (res + 15LL * modpow(2, n - 2)) % mod;
else res = (res + 16LL * modpow(3, m - n - 1) % mod * modpow(2, n - 1) + 12LL * (modpow(2, n - 2) + modpow(2, n - 1) * (modpow(3, m - n - 1) - 1) % mod * (mod + 1) / 2 % mod)) % mod;
res = (res + 20LL * modpow(3, m - n) % mod * modpow(2, n - 1) % mod * (modpow(4, n - 4) - 1) % mod * (mod + 1) / 3) % mod;
if (n == m) res = (res + 15LL * modpow(2, n - 2)) % mod;
else res = (res + 20LL * modpow(3, m - n - 1) % mod * modpow(2, n - 1)) % mod;
return printf("%lld\n", res * 2 % mod) * 0;
return 0;
}
T6 保卫王国
题目链接
考场上看完题就只剩15min了,于是连裸的dp都写挂了呜呜呜……
好吧这道题的确可以动态dp,不过我只会
的ddp诶,肯定就算写出来也会被卡常。(听说可以LCT维护ddp?听起来是个好办法……不过也感觉细节好多好难写啊)
下面讲个比较NOIP的思路吧……
令倍增数组
表示从点
算起往上到第
个点的子树中,子树根状态为0/1,点
状态为0/1时的最小值。
再令
表示除去
的子树(不包括
),点
的状态为0/1时剩下连通块的最小值。
以下的区间均表示一条链。有了上面两个东西,对于查询的两个点
,求出其
,如果一个是另一个的father(不妨令
是
的father),我们显然可以利用
在
的时间内合并出
的答案,并且还可以确定
的状态。接下来就可以根据
求出
除去含
的那个儿子所在子树后的最小值。合并这两个答案即可。
否则,分别统计出
和
的答案,然后枚举lca的状态,利用
和上面统计的两个答案就可以合并出答案了。总复杂度
,不过常数似乎很大?
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 100005;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
struct Edge { int to, next; } edge[maxn * 2];
int head[maxn], p[maxn], par[20][maxn], dep[maxn], tot, n, m;
ll f[maxn][2];
struct Number {
ll a[2][2] = {{INF, INF}, {INF, INF}}; int u;
Number operator-(pair<ll, ll> p) const {
Number res = *this;
p.first = min(p.first, p.second);
res.a[0][0] -= p.second, res.a[0][1] -= p.second;
res.a[1][0] -= p.first, res.a[1][1] -= p.first;
return res;
}
Number operator+(const Number &n) const {
Number res; res.u = n.u;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
res.a[i][j] = min(INF, min(min(a[i][0], a[i][1]) + n.a[1][j], a[i][1] + min(n.a[0][j], n.a[1][j])));
return res;
}
} st[20][maxn];
void addedge(int u, int v) {
edge[++tot] = (Edge) { v, head[u] };
head[u] = tot;
}
void dfs(int u, int fa) {
dep[u] = dep[par[0][u] = fa] + 1;
st[0][u].u = u;
st[0][u].a[0][0] = 0;
st[0][u].a[1][1] = p[u];
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) continue;
dfs(v, u);
st[0][u].a[0][0] += st[0][v].a[1][1];
st[0][u].a[1][1] += min(st[0][v].a[0][0], st[0][v].a[1][1]);
}
st[0][u].a[0][0] = min(st[0][u].a[0][0], INF);
st[0][u].a[1][1] = min(st[0][u].a[1][1], INF);
}
void efs(int u, int fa) {
ll a = st[0][u].a[0][0], b = st[0][u].a[1][1];
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) continue;
ll ta = f[u][1] + b - min(st[0][v].a[0][0], st[0][v].a[1][1]) - p[u];
ll tb = f[u][0] + a - st[0][v].a[1][1];
f[v][0] = ta, f[v][1] = min(ta, tb) + p[v];
efs(v, u);
}
}
char type[5];
int get_lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int i = 19; i >= 0; i--) if (dep[par[i][u]] >= dep[v]) u = par[i][u];
if (u == v) return u;
for (int i = 19; i >= 0; i--) if (par[i][u] != par[i][v]) u = par[i][u], v = par[i][v];
return par[0][u];
}
Number get_ans(int u, int d) {
Number res = st[0][u]; --d; u = par[0][u];
for (int i = 19; i >= 0; i--) if (d >= (1 << i))
d -= 1 << i, res = res + (st[i][u] - make_pair(st[0][res.u].a[0][0], st[0][res.u].a[1][1])), u = par[i][u];
return res;
}
int main() {
scanf("%d%d%s", &n, &m, type);
for (int i = 1; i <= n; i++) scanf("%d", p + i);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
}
dfs(1, 0);
f[1][1] = p[1];
efs(1, 0);
for (int i = 1; i < 20; i++)
for (int j = 1; j <= n; j++) {
par[i][j] = par[i - 1][par[i - 1][j]];
int u = st[i - 1][j].u;
st[i][j] = st[i - 1][j] + (st[i - 1][par[i - 1][j]] - make_pair(st[0][u].a[0][0], st[0][u].a[1][1]));
}
while (m--) {
int a, x, b, y;
scanf("%d%d%d%d", &a, &x, &b, &y);
if (dep[a] < dep[b]) swap(a, b), swap(x, y);
int l = get_lca(a, b);
if (l == b) {
Number num = get_ans(a, dep[a] - dep[l]);
int la = num.u;
ll t = f[l][y] + st[0][l].a[y][y] - (y ? min(st[0][la].a[0][0], st[0][la].a[1][1]) + p[l] : st[0][la].a[1][1]);
ll res = t + (y ? min(num.a[x][0], num.a[x][1]) : num.a[x][1]);
printf("%lld\n", res >= 1E12 ? -1 : res);
} else {
Number na = get_ans(a, dep[a] - dep[l]), nb = get_ans(b, dep[b] - dep[l]);
int la = na.u, lb = nb.u;
ll ta = f[l][0] + st[0][l].a[0][0] - st[0][la].a[1][1] - st[0][lb].a[1][1];
ll tb = f[l][1] + st[0][l].a[1][1] - min(st[0][la].a[0][0], st[0][la].a[1][1]) - min(st[0][lb].a[0][0], st[0][lb].a[1][1]) - p[l];
ll res = min(ta + na.a[x][1] + nb.a[y][1], tb + min(na.a[x][0], na.a[x][1]) + min(nb.a[y][0], nb.a[y][1]));
printf("%lld\n", res >= 1E12 ? -1 : res);
}
}
return 0;
}