2024/11/17 总结
记忆宫殿
初始入度为\(0\)的点为源点。
我们令 \(S\) 为如果成立,就能够推出事件的源点集合。
当事件成立时,显然 \(S\) 中的点必有至少一个是真的。所以我们只要把所有包含 \(S\) 的事件都标记为真就行了。
Pro-Professor Szu
某大学校内有一栋主楼,还有 栋住宅楼。这些楼之间由一些单向道路连接,但是任意两栋楼之间可能有多条道路,也可能存在起点和终点为同一栋楼的环路。存在住宅楼无法到达主楼的情况。
现在有一位古怪的教授,他希望每天去主楼上班的路线不同。
一条上班路线中,每栋楼都可以访问任意多次。我们称两条上班路线是不同的,当且仅当两条路线中存在一条路是不同的(两栋楼之间的多条道路被视为是不同的道路)。
现在教授希望知道,从哪些住宅楼前往主楼的上班路线数最多。
到达n+1后,可以选择停下或者继续走
首先发现若一个大小大于 1 的 SCC 或自环(下称为不合法结构)能够到达教学楼,则该不合法结构内部每个点到教学楼的路径数量都是无穷大。
显然可以先缩点,然后拓扑排序。
先将反图上入度为 0 的非教学楼点入队跑一遍拓扑排序。注意此时不合法结构可以入队,因为它们没有到达教学楼的路径。
最后,若出现没有入队的点,说明这个点能够到达一个不合法结构,因此路径数同样为无穷大。此外,若 \(f_i>36500\) 也不符合题意。
自己的代码没调出来,一直不过hack,贴一个题解的,过了就补(已)
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, ed, ban[N], deg[N], f[N];
int dn, dfn[N], low[N], cn, col[N], top, stc[N], vis[N];
struct linklist {
int cnt, hd[N], nxt[N], to[N];
void add(int u, int v) {nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v;}
} e, g;
void tarjan(int id) {
dfn[id] = low[id] = ++dn, stc[++top] = id, vis[id] = 1; // 0 -> 1
for(int _ = e.hd[id]; _; _ = e.nxt[_]) {
int it = e.to[_];
if(!dfn[it]) tarjan(it), low[id] = min(low[id], low[it]);
else if(vis[it]) low[id] = min(low[id], dfn[it]);
}
if(low[id] == dfn[id]) {
col[id] = ++cn, ban[cn] = stc[top] != id;
while(stc[top] != id) col[stc[top]] = cn, vis[stc[top--]] = 0; // id -> cn
vis[id] = 0, top--;
}
}
int main() {
#ifdef ALEX_WEI
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
e.add(u, v);
}
for(int i = 1; i <= n + 1; i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= n + 1; i++)
for(int _ = e.hd[i]; _; _ = e.nxt[_]) {
int it = e.to[_];
if(i == it) ban[col[i]] = 1;
else if(col[i] != col[it]) g.add(col[it], col[i]), deg[col[i]]++;
}
ed = col[n + 1];
queue<int> q;
for(int i = 1; i <= cn; i++) if(i != ed && !deg[i]) q.push(i);
memset(vis, 0, sizeof(vis));
while(!q.empty()) {
int t = q.front();
q.pop(), vis[t] = 1;
for(int _ = g.hd[t]; _; _ = g.nxt[_]) {
int it = g.to[_];
if(!--deg[it] && it != ed) q.push(it);
}
}
if(!ban[ed]) assert(!deg[ed]), q.push(ed), f[ed] = 1;
while(!q.empty()) {
int t = q.front();
q.pop(), vis[t] = 1;
for(int _ = g.hd[t]; _; _ = g.nxt[_]) {
int it = g.to[_];
if(ban[it]) continue;
f[it] = min(36501, f[it] + f[t]);
if(!--deg[it]) q.push(it);
}
}
vector<int> ans;
for(int i = 1; i <= n; i++)
if(!vis[col[i]] || f[col[i]] == 36501)
ans.push_back(i);
if(!ans.empty()) puts("zawsze");
else {
int mx = 0;
for(int i = 1; i <= n; i++) {
if(f[col[i]] > mx) mx = f[col[i]], ans.clear();
if(f[col[i]] == mx) ans.push_back(i);
}
cout << mx << "\n";
}
cout << ans.size() << "\n";
for(int it : ans) cout << it << " ";
return cerr << "Time: " << clock() << endl, 0;
}
POI2010 Antisymmetry
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
manacher板子,写就完了
#include <bits/stdc++.h>
#define ull long long
const int maxn = 1e7;
char SS1[maxn], S[maxn], to[500];
int n, len[maxn], tot = 1;
signed main() {
scanf("%d%s", &n, SS1 + 1);
S[0] = '$', S[1] = '#';
for (register int i = 1; i <= n; ++i) S[++tot] = SS1[i], S[++tot] = '#';
to['1'] = '0', to['0'] = '1', to['#'] = '#', to['$'] = '$';
int pos = 1, mx = 1;
ull ans = 0;
for (register int i = 1; i <= tot; i += 2) {
len[i] = (i < mx ? std::min(mx - i, len[(pos << 1) - i]) : 1);
while (S[i + len[i]] == to[S[i - len[i]]]) len[i]++;
if (len[i] + i > mx) {
mx = len[i] + i;
pos = i;
}
ans += len[i] >> 1;
}
printf("%llu\n", ans);
return 0;
}