CSP-S2考前综合强化刷题(第四场)
D实在不会orzljhwyz
A
显然可以二分答案变成判定问题
然后考虑解决一下怎么两两可达,显然有向图是强联通分量,所有点在一个强联通分量就行
当然也可以从一出发能到达所有点,然后反图中从所有点出发能到达1就行
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 6e4 + 7;
const int MAXM = 3e5 + 7;
int n, m, ccnt, mid;
struct rec {
int x, y, z;
bool operator<(const rec &w)const {
return z < w.z;
}
} e[MAXM];
int home[MAXN], nxt[MAXM], to[MAXM];
int dfn[MAXN], low[MAXN], fl[MAXN];
int bel[MAXN], st[MAXN], tp, depp;
namespace fastIO {
#define BUF_SIZE (1<<20)
static char buf[BUF_SIZE], *p1 = buf, *pend = buf;
inline char nc() {
if(p1 == pend) {
p1 = buf;
pend = buf + fread(buf, 1, BUF_SIZE, stdin);
}
return *p1++;
}
inline int read() {
int x = 0;
char s = nc();
for(; !isdigit(s); s = nc());
for(; isdigit(s); s = nc())x = (x << 1) + (x << 3) + s - '0';
return x;
}
}
using namespace fastIO;
inline void ct(int x, int y) {
ccnt++;
nxt[ccnt] = home[x];
home[x] = ccnt;
to[ccnt] = y;
return;
}
inline void init() {
memset(dfn, 0, sizeof(dfn));
memset(fl, 0, sizeof(fl));
memset(low, 0, sizeof(low));
memset(home, 0, sizeof(home));
memset(bel, 0, sizeof(bel));
ccnt = 0;
tp = 0;
depp = 0;
}
inline void tarjan(int u) {
st[++tp] = u;
dfn[u] = low[u] = ++depp;
fl[u] = 1;
for(int i = home[u]; i; i = nxt[i]) {
int v = to[i];
if(!dfn[v]) {
tarjan(v);
} else if(!fl[v])continue;
low[u] = min(low[u], low[v]);
}
if(low[u] == dfn[u]) {
while(st[tp] != u) {
int v = st[tp];
--tp;
bel[v] = u;
fl[v] = 0;
}
--tp;
bel[u] = u;
fl[u] = 0;
}
return ;
}
inline int chk(int x) {
init();
for(int i = 1; i <= x; ++i) {
ct(e[i].x, e[i].y);
}
for(int i = 1; i <= n; ++i) {
if(!dfn[i]) {
tarjan(i);
}
}
for(int i = 1; i <= n; ++i) {
if(bel[i] != bel[1]) {
return 0;
}
}
return 1;
}
int main() {
n = read();
m = read();
for(int i = 1; i <= m; ++i) {
e[i].x = read();
e[i].y = read();
e[i].z = read();
}
sort(e + 1, e + m + 1);
int l = 1, r = m, ans = -1;
while(l <= r) {
mid = (l + r) >> 1;
if(chk(mid)) {
r = mid - 1;
ans = mid;
} else {
l = mid + 1;
}
}
printf("%d\n", e[ans].z);
return 0;
}
B
NOIP树上计数都是...
首先有做法,就是从一个端点开始dfs,然后我们考虑dfs到另一个点的时候就可知道路径了
那么我们维护一个到根的a的和,然后用每个b去乘上a的和即可
这个式子可以求出(x,y)路径所有a的和*所有a的和,然后减去每个数自己的平方和
然后你发现我们算了两边,可以除以二就是答案
std:
考场做法:
考虑我们跨过lca的很好算,就是右边的b的和*左边的a的和
但是会发现两个点在一边的不太好搞?
于是可以统计一个即(每个点i*到根的的和)的前缀和
然后你会发现在x的那一边的就可以算了,相当于这个前缀和相减然后再减去lca以上多算的
然后y那一边会发现变得和y相关而不是lca相关??
灵机一动会发现这个如果我们维护就能变换成x那边a的那种形式了
所以这三部分求和就是答案了
树上倍增std做法?
可以避免前缀和?
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 2e5 + 7;
const int MAXM = 4e5 + 7;
int n, ccnt, home[MAXN], nxt[MAXM], to[MAXM], m;
int fa[MAXN], a[MAXN], b[MAXN];
ll suma[MAXN], sumb[MAXN], sumab[MAXN], sumba[MAXN];
int son[MAXN], siz[MAXN], top[MAXN], dep[MAXN];
inline void ct(int x, int y) {
ccnt++;
nxt[ccnt] = home[x];
home[x] = ccnt;
to[ccnt] = y;
}
inline void dfs1(int u) {
suma[u] += a[u];
sumb[u] += b[u];
sumab[u] += 1ll * a[u] * sumb[fa[u]];
sumba[u] += 1ll * b[u] * suma[fa[u]];
siz[u] = 1;
for(int i = home[u]; i; i = nxt[i]) {
int v = to[i];
suma[v] = suma[u];
sumb[v] = sumb[u];
sumab[v] = sumab[u];
sumba[v] = sumba[u];
dep[v] = dep[u] + 1;
dfs1(v);
siz[u] += siz[v];
if(siz[son[u]] < siz[v])son[u] = v;
}
return ;
}
inline void dfs2(int u, int topf) {
top[u] = topf;
if(!son[u])return ;
dfs2(son[u], topf);
for(int i = home[u]; i; i = nxt[i]) {
int v = to[i];
if(v == son[u])continue;
dfs2(v, v);
}
}
inline int LCA(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]])swap(x, y);
x = fa[top[x]];
}
if(dep[x] > dep[y])swap(x, y);
return x;
}
inline ll getans(int x, int y) {
int z = LCA(x, y);
ll ans = 0;
ans += sumab[x] - sumab[z] - sumb[fa[z]] * (suma[x] - suma[z]);
ans += sumba[y] - sumba[z] - suma[fa[z]] * (sumb[y] - sumb[z]);
ans += (suma[x] - suma[z]) * (sumb[y] - sumb[z]);
return ans;
}
int main() {
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 2; i <= n; ++i) {
scanf("%d", &fa[i]);
ct(fa[i], i);
}
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; ++i) {
scanf("%d", &b[i]);
}
dfs1(1);
dfs2(1, 1);
for(int i = 1, x, y; i <= m; ++i) {
scanf("%d%d", &x, &y);
printf("%lld\n", getans(x, y));
}
return 0;
}
C
显然可以动态规划?
表示前i个格子,第i个染了j这种颜色然后有没有用那一次,用了的话长多少
转移或者前缀和优化都可以,都屑屑屑
仔细想这个l没有用,我们只需要考虑k是直接染完还是之前就染完就好了,没有必要拖到后面再染
所以表示前i个格子,第i个染了j这种颜色然后有没有用那一次
第一个转移是可以转移来,用个前缀最小值和后缀最小值优化一下就好了
然后我们又有一个转移是
这个k是一段区间,可以单调队列优化
老师讲的我们可以枚举一段区间和颜色,然后再考虑左右两边都不能选相同就做完了
code:
//前缀和优化 + 单调队列优化
//可以做到O(nm)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace fastIO {
#define BUF_SIZE (1<<20)
static char buf[BUF_SIZE], *p1 = buf, *pend = buf;
inline char nc() {
if(p1 == pend) {
p1 = buf;
pend = buf + fread(buf, 1, BUF_SIZE, stdin);
}
return *p1++;
}
inline int read() {
int x = 0;
char s = nc();
for(; !isdigit(s); s = nc());
for(; isdigit(s); s = nc())x = (x << 1) + (x << 3) + s - '0';
return x;
}
}
using namespace fastIO;
const int MAXN = 2050;
const ll inf = 1e18;
int n, m, K, w[MAXN][MAXN];
ll dp[MAXN][MAXN][2], sum[MAXN][MAXN];
ll mip[MAXN][MAXN][2], mis[MAXN][MAXN][2];
int que[MAXN][MAXN * 2], fr[MAXN], ed[MAXN];
int main() {
n = read();
m = read();
K = read();
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
w[i][j] = read();//i - > j
sum[i][j] = sum[i - 1][j] + w[i][j];
}
}
memset(dp, 0x3f3f3f3f, sizeof(dp));
memset(mis, 0x3f3f3f3f, sizeof(mis));
memset(mip, 0x3f3f3f3f, sizeof(mip));
for(int i = 1; i <= m; ++i) {
dp[0][i][0] = 0;
mip[0][i][0] = 0;
mis[0][i][0] = 0;
fr[i] = ed[i] = 1;//一开始有决策0
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
while(que[j][fr[j]] < i - K + 1 && fr[j] <= ed[j]) {
++fr[j];
}//过时决策
}
for(int j = 1; j <= m; ++j) {
dp[i][j][0] = min(dp[i][j][0], min(mip[i - 1][j - 1][0], mis[i - 1][j + 1][0]) + w[i][j]);
//0的转移- > 直接继承,前缀和优化
dp[i][j][1] = min(dp[i][j][1], min(mip[i - 1][j - 1][1], mis[i - 1][j + 1][1]) + w[i][j]);
//在此之前我们就已经有了
if(fr[j] <= ed[j]) {
int k = que[j][fr[j]];
dp[i][j][1] = min(dp[k][j][0] + sum[i][j] - sum[k][j], dp[i][j][1]);
}
//从这里另起一段
}
for(int j = 1; j <= m; ++j) {
mip[i][j][0] = min(mip[i][j - 1][0], dp[i][j][0]);
mip[i][j][1] = min(mip[i][j - 1][1], dp[i][j][1]);
}
for(int j = m; j >= 1; --j) {
mis[i][j][0] = min(mis[i][j + 1][0], dp[i][j][0]);
mis[i][j][1] = min(mis[i][j + 1][1], dp[i][j][1]);
}
for(int j = 1; j <= m; ++j) {
while(dp[que[j][ed[j]]][j][0] - sum[que[j][ed[j]]][j] >= dp[i][j][0] - sum[i][j] && fr[j] <= ed[j]) {
--ed[j];
}//不优决策,下一秒i至少比她好看
++ed[j];
que[j][ed[j]] = i;//加入决策i
}
}
ll ans = inf;
for(int i = 1; i <= m; ++i)ans = min(dp[n][i][0], min(dp[n][i][1], ans));
printf("%lld\n", ans);
return 0;
}
D
考场上使用IDA*写了20/kk
然后出考场人均70/ll
首先这个题不需要AC自动机上计数什么毒瘤东西,因为所有串长都相同
我们会发现如果把所有的串写出来的话,我们可以计算出从一个串到另一个串的代价
然后如果把这个东西建成一张图
那么好像答案就是把所有的好串都经过一遍的最短路径....
TSP哎QAQ
显然我们坏串就是不被经过的点,能预处理出来
然后状压一下有哪些点是已经被经过的,最后停留在那个点,然后我们下一个点走到那个关键点预处理一下即可
时间复杂度
TSP状压注意下标平移的问题,以及初始化
code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 7;
int n, m, k;
int bd[MAXN], g[MAXN];
int vis[MAXN];
int ccnt, home[MAXN], nxt[MAXN], to[MAXN], w[MAXN];
inline void ct(int x, int y, int z) {
if(bd[x] || bd[y])return ;//bad
ccnt++;
nxt[ccnt] = home[x];
home[x] = ccnt;
to[ccnt] = y;
w[ccnt] = z;
}
int T = 1;
struct machine {
int ch[MAXN][5], fa[MAXN], rt = 1;
inline void ins(char *c, int l, int id) {
int nw = 1;
for(int i = 0; i < l; ++i) {
int t = c[i] - '1';
if(!ch[nw][t])ch[nw][t] = ++T;
nw = ch[nw][t];
}
if(id)g[id] = nw;
else bd[nw] = 1;
}
inline void init() {
static queue<int> q;
for(int i = 0; i < 4; ++i) {
if(ch[rt][i]) {
fa[ch[rt][i]] = rt;
q.push(ch[rt][i]);
} else
ch[rt][i] = rt;
}
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = 0; i < 4; ++i) {
if(ch[u][i]) {
fa[ch[u][i]] = ch[fa[u]][i];
q.push(ch[u][i]);
} else ch[u][i] = ch[fa[u]][i];
}
}
for(int i = 1; i <= T; ++i) {
for(int j = 0; j < 4; ++j) {
ct(i, ch[i][j], j + 1);
}
}
return;
}
} ac;
#define pii pair<int,int>
#define mkp(x,y) (make_pair(x,y))
#define fi first
#define se second
char s[123];
int dis[21][MAXN];
inline void dij(int *dis, int s) {
static priority_queue<pii, vector<pii>, greater<pii> > q;
for (int i = 1; i <= T; ++i)dis[i] = 1e9, vis[i] = 0;
q.push(mkp(dis[s] = 0, s));
while(!q.empty()) {
int u = q.top().se;
q.pop();
if(vis[u])continue;
vis[u] = 1;
for(int i = home[u]; i; i = nxt[i]) {
int v = to[i];
if(bd[v])continue;
if(dis[v] > dis[u] + w[i]) {
dis[v] = dis[u] + w[i];
q.push(mkp(dis[v], v));
}
}
}
return ;
}
int f[20][(1 << 20) + 5];
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i) {
scanf("%s", s);
ac.ins(s, k, i);
}
for(int i = 1; i <= m; ++i) {
scanf("%s", s);
ac.ins(s, k, 0);
}
ac.init();
dij(dis[0], 1);
for(int i = 1; i <= n; ++i) {
dij(dis[i], g[i]);
}
memset(f, 0x3f, sizeof(f));
int MS = (1 << n) - 1;
for(int i = 0; i < n; ++i) {
f[i][(1 << i)] = dis[0][g[i + 1]];
}
for(int S = 1; S <= MS; ++S) {
for(int i = 0; i < n; ++i) {
if(f[i][S] > 1e9)continue;
if(S >> i & 1) {
for(int j = 0; j < n; ++j) {
if(S >> j & 1)continue;
f[j][S | (1 << j)] = min(f[j][S | (1 << j)], f[i][S] + dis[i + 1][g[j + 1]]);
}
}
}
}
int ans = 1e9;
for(int i = 0; i < n; ++i)ans = min(ans, f[i][MS]);
printf("%d\n", ans);
return 0;
}
code抄袭吴队爽