0930下午考试
0930下午考试
T1
题目大意:
题面又长又臭,AcWing上有原题:闇の連鎖
这题海星,写出来了。其实正解是树上差分,但是我乱搞了一下,其实思想一样,就是实现方式不太相同。
我的歪解:我们遍历每一条白边,假如这条白边是割边,那么它将会有\(m\)的贡献,因为它可以和每一条黑边组成合法的。假如他不是割边,我们算一下这条边连接的子节点的子树内有几条黑边连向了子树外边,如果只有一条黑边连了出去,那么它的贡献就是1,因为它只能与这条黑边组成一个合法的。如果有大于等于两条黑边连了出去,那么它的贡献就是0,因为两颗炮弹不可能打断三条及以上的边。
复杂度\(O(nlogn)\)。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3e5 + 5, M = 1e6 + 5;
int n, m, cnt, tot;
long long ans;
int f[N], b[M], head[N], dfn[N], low[N], siz[N], num[N], sx[N];
struct edge { int to, nxt, val; } e[M];
void add(int x, int y, int z) {
e[++cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].val = z;
}
void Tarjan(int x, int in_e) {
low[x] = dfn[x] = ++ tot;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(!dfn[y]) {
Tarjan(y, i), low[x] = min(low[x], low[y]);
if(low[y] > dfn[x]) b[i] = b[i ^ 1] = 1;
}
else if(i != (in_e ^ 1)) low[x] = min(low[x], dfn[y]);
}
}
void get_tree(int x, int fa) {
siz[x] = 1; f[x] = fa; sx[x] = ++ tot;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to, col = e[i].val;
if(y == fa || col == 0) continue;
get_tree(y, x);
siz[x] += siz[y];
}
}
void dfs(int x, int fa) {
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to, col = e[i].val;
if(y == fa || col == 0) continue;
dfs(y, x);
int tmp = ans;
if(dfn[x] < low[y]) ans += m;
else {
for(int j = head[y]; j; j = e[j].nxt) {
int to = e[j].to, col2 = e[j].val;
if(col2 == 1) continue;
int now = y;
while(sx[to] < sx[now] && now) num[now] ++, now = f[now]; //暴力向上跳来修改有几条黑边连向了now子树以外的子树
now = y;
while(sx[to] >= sx[now] + siz[now] && now) num[now] ++, now = f[now];
}
if(num[y] == 0) ans += m;
if(num[y] == 1) ans += 1;
}
}
}
int main() {
n = read(); m = read(); cnt = 1;
for(int i = 1, x, y;i <= n - 1; i++) {
x = read(); y = read();
add(x, y, 1); add(y, x, 1);
}
for(int i = 1, x, y;i <= m; i++) {
x = read(); y = read();
add(x, y, 0); add(y, x, 0);
}
Tarjan(1, 0); tot = 0;
get_tree(1, 0); dfs(1, 0);
printf("%lld", ans);
fclose(stdin); fclose(stdout);
return 0;
}
好像\(Aswert\)大佬讲过这个题,但我没听2333
T2
题目大意:
有一天,你要去找 夹克老爷 玩,但是发现他并不在家,所以你打算回家刷题,发现 绿色夹克蛤 给你制造了一个迷宫。这个迷宫满足下面的几个性质: 这是一张 n 个点 m 条边的连通图。2. 这张图上面有 k 个奇葩点。3. 保证没有重边和自环。由于你想回家,所以你为了让 绿色夹克蛤 快乐,你决定帮他解决一道难题。绿色夹克蛤 现在要求你从其中任意一个奇葩点开始走,走到除了这个奇葩点以外的最近奇葩点。问选择哪一个奇葩点开始走,路程最小。
分组多源最短路。
这种题做了第三遍了,还是没做出来,这可不太妙。
因为每一个奇葩点标号都不同,所以他们的二进制位上肯定有某几位不同,我们按二进制分组,当前位上为1的为源点,算出当前位上为0的最短路。这么分组可能会重复,但一定不会漏。
有个大佬跑了\(n\)遍最短路,加了个优化过了?????我tm直接疑惑。其实他就是每遍跑最短路的时候,如果除了起点,第一次从队列里弹出奇葩点,就直接\(break\),因为第一次弹出的一定为最小的。(%%%)
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5, M = 2e5 + 5, inf = 2e9;
int n, m, k, cnt, ans;
int a[N], dis[N], head[N], vis[N];
struct edge { int to, nxt, val; } e[M << 1];
void add(int x, int y, int z) {
e[++cnt].nxt = head[x]; e[cnt].to = y; e[cnt].val = z; head[x] = cnt;
}
void run_dij(int w) {
for(int i = 1;i <= n; i++) dis[i] = inf, vis[i] = 0;
priority_queue <pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
for(int i = 1;i <= k; i++)
if((a[i] >> w) & 1) q.push(make_pair(0, a[i])), dis[a[i]] = 0;
while(!q.empty()) {
int x = q.top().second; q.pop();
if(vis[x]) continue; vis[x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(dis[y] > dis[x] + e[i].val) {
dis[y] = dis[x] + e[i].val;
q.push(make_pair(dis[y], y));
}
}
}
for(int i = 1;i <= k; i++)
if(!((a[i] >> w) & 1)) ans = min(ans, dis[a[i]]);
}
int main() {
n = read(); m = read(); k = read();
for(int i = 1, x, y, z;i <= m; i++) {
x = read(); y = read(); z = read();
add(x, y, z); add(y, x, z);
}
for(int i = 1;i <= k; i++) a[i] = read();
ans = inf;
for(int i = 0;i <= 16; i++) run_dij(i);
printf("%d", ans);
fclose(stdin); fclose(stdout);
return 0;
}
T3
题目大意:
帮助统治者解决问题之后,统治者准备奖励你两把剑,让你去打怪。具体的来说,两把剑分别代表了两个长度为n的序列a,b。你什么方面都强,所以你可以分别重新锻造这两把剑,锻造就相当于重新排列这两个序列。合并这两把剑,让它变成一把新剑(对应序列c),合并相当于把对应位置上的数加起来c[i]=a[i]+b[i]。最后你准备拿着这把新剑去找大Boss,造成的伤害是众数出现的次数。问怎么排列才能使得伤害最大化,输出最大伤害。
暴力,这题真暴力,\(n ^ 2\)过四万,就离谱????
正解要用到NTT,我太弱了不会。但直接暴力就过了,时限1.5秒。(很玄学是不是)
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5;
int n, ans, a[N], b[N], vis[N], vis1[N], vis2[N];
int main() {
n = read();
int t3 = 0, t4 = 0;
for(int i = 1;i <= n; i++) a[i] = read(), vis1[a[i]] ++, t3 = max(t3, a[i]);
for(int i = 1;i <= n; i++) b[i] = read(), vis2[b[i]] ++, t4 = max(t4, b[i]);
for(int i = 1;i <= t3; i++)
for(int j = 1;j <= t4; j++)
vis[i + j] += min(vis1[i], vis2[j]);
for(int i = 1;i <= t3 + t4; i++)
ans = max(ans, vis[i]);
printf("%d", ans);
fclose(stdin); fclose(stdout);
return 0;
}