2024/11/17 总结

记忆宫殿

image

初始入度为\(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;
}
posted @ 2024-11-17 21:43  Dreamers_Seve  阅读(4)  评论(0编辑  收藏  举报