​困难的图​​​ 法1:缩点双联通,如果边数正好等于点数,说明是一个简单环
法2:CSP-S模拟 19/10/12_线段树出一棵树,考虑非树边的影响,如果有一条边被两条非树边覆盖,那么不走这条边,那两条非树边会连成另一个环,如果非树边去覆盖树边的话,简单环存在当且仅当这一条链只被经过一次
差分一下一条边经过几次即可,非树边都是反祖边

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, m;
int first[N], nxt[N << 1], to[N << 1], w[N << 1], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
struct edge{ int u, v, in, flg; } e[N];
int dep[N], d[N], fa[N], id[N]; bool vis[N];
void dfs(int u, int f){
vis[u] = 1;
for(int i = first[u]; i; i = nxt[i]){
int t = to[i]; if(vis[t] || t == f) continue;
dep[t] = dep[u] + 1; fa[t] = u; id[t] = i;
e[(i + 1) >> 1].in = 1;
dfs(t, u);
}
}
void calc(int u, int f){
vis[u] = 1;
for(int i = first[u]; i; i = nxt[i]){
int t = to[i]; if(vis[t] || t == f) continue;
calc(t, u); d[u] += d[t];
}
}
void get(int u, int f){
vis[u] = 1;
for(int i = first[u]; i; i = nxt[i]){
int t = to[i]; if(vis[t] || t == f) continue;
d[t] += d[u]; get(t, u);
}
}
void able(int v, int u){ while(v^u){ e[(id[v] + 1) >> 1].flg = 1; v = fa[v];} }
int main(){
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= m; i++){
int x = read(), y = read();
e[i].u = x, e[i].v = y;
add(x, y); add(y, x);
}
for(int i = 1; i <= n; i++) if(!vis[i]) dfs(i, 0);
for(int i = 1; i <= m; i++){
if(e[i].in) continue;
int u = e[i].u, v = e[i].v;
if(dep[u] > dep[v]) swap(u, v);
++d[v]; --d[u];
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++) if(!vis[i]) calc(i, 0);
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++) if(!vis[i]) get(i, 0);
for(int i = 1; i <= m; i++){
if(e[i].in) continue;
int u = e[i].u, v = e[i].v;
if(dep[u] > dep[v]) swap(u, v);
if(dep[v] - dep[u] - (d[v] - d[u]) == 0) able(v, u), e[i].flg = 1;
}
int ans = 0;
for(int i = 1; i <= m; i++) if(e[i].flg) ans ^= i;
cout << ans; return 0;
}

​中等的字符串​​​CSP-S模拟 19/10/12_i++_02 就是 AC 自动机的板子,CSP-S模拟 19/10/12_git_03 表示走 CSP-S模拟 19/10/12_线段树_04 步,到 CSP-S模拟 19/10/12_git_05 的最大值,枚举字符转移
CSP-S模拟 19/10/12_线段树_06
发现总点数 CSP-S模拟 19/10/12_git_07CSP-S模拟 19/10/12_git_08 ,这启示我们用矩阵乘
CSP-S模拟 19/10/12_线段树_09 的矩阵乘改成 CSP-S模拟 19/10/12_git_10 即可,同样满足结合律

#include<bits/stdc++.h>
#define N 205
using namespace std;
typedef long long ll;
ll read(){
ll cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
ll n; int m, a[N];
int ch[N][26], fail[N], val[N], tot;
void Mx(ll &a, ll b){ if(a < b) a = b;}
struct matrix{
ll a[N][N];
matrix(){ memset(a, -0x3f, sizeof(a));}
matrix operator * (const matrix &A){
matrix B; for(int i = 0; i <= tot; i++){
for(int j = 0; j <= tot; j++){
for(int k = 0; k <= tot; k++){
Mx(B.a[i][j], a[i][k] + A.a[k][j]);
}
}
} return B;
}
};
void ins(int k, string s){
int len = s.length(), now = 0;
for(int i = 0; i < len; i++){
int c = s[i] - 'a'; if(!ch[now][c]) ch[now][c] = ++tot;
now = ch[now][c];
} val[now] += a[k];
}
void build(){
queue<int> q;
for(int i = 0; i < 26; i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty()){
int x = q.front(); q.pop();
val[x] += val[fail[x]];
for(int i = 0; i < 26; i++){
if(ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]);
else ch[x][i] = ch[fail[x]][i];
}
}
}
matrix power(matrix A, ll b){
matrix ans; for(int i = 0; i <= tot; i++) ans.a[i][i] = 0;
for(;b; b>>=1, A = A * A) if(b&1) ans = ans * A; return ans;
}
int main(){
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= m; i++) a[i] = read();
for(int i = 1; i <= m; i++){
string s; cin >> s; ins(i, s);
} build();
matrix A;
for(int i = 0; i <= tot; i++)
for(int j = 0; j < 26; j++){
int nxt = ch[i][j]; A.a[i][nxt] = val[nxt];
}
A = power(A, n);
matrix B; B.a[0][0] = 0; B = B * A;
ll ans = 0;
for(int i = 0; i <= tot; i++) ans = max(ans, B.a[0][i]);
cout << ans; return 0;
}

​上网​​​ 想到了线段树优化建图,但是不能 k 个点都向线段树上连边
因为 k 个点会把线段树分成 CSP-S模拟 19/10/12_git_11 段,直接连边是CSP-S模拟 19/10/12_git_12
考虑到 k 个点向线段树连的都是同样的边,我们可以对每个限制建一个虚点,这 k 个点向虚点连边,边权为0,虚点向线段树连边,边权为1,边权的意义是至少比它大多少,然后拓扑排序即可
兴致勃勃写完后发现,由于有线段树优化建图的存在,没有点度数为 0,然后头秃了一小会儿
后来把程序改成了,虚点向 k 个点连边,线段树向虚点连边,线段树本身从下往上连
这样就存在度数为 0 的点并且度数为 0 的点一定是最小的
然后拓扑排序往后面走,记录每个点至少是多少并更新后面的,如果跟原值冲突(比它大),那么不合法
我是从 0 开始赋权值的,但是题目
”小 L 只记得士兵的防守值是位于CSP-S模拟 19/10/12_git_13 之间的整数"
然后直接 0 分滚蛋,关键是暴力思路跟正解一样,对拍还拍不出来
反思:以后不仅要对拍,还要多读几遍题目,把题目的限制搞得明明白白再开始写

#include<bits/stdc++.h>
#define N 1000050
#define M 6000050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int n, s, m, a[N], node, val[N];
int first[N], nxt[M], to[M], w[M], tot;
int du[N];
int nd[N];
void add(int x, int y, int z){ du[y]++; nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;}
void build(int x, int l, int r){
if(l == r){ nd[x] = l; return; }
nd[x] = ++node;
int mid = (l+r) >> 1; build(x<<1, l, mid); build(x<<1|1, mid+1, r);
add(nd[x << 1], nd[x], 0); add(nd[x << 1 | 1], nd[x], 0);
}
void modify(int x, int l, int r, int L, int R, int pos){
if(L<=l && r<=R){ add(nd[x], pos, 1); return; }
int mid = (l+r) >> 1;
if(L<=mid) modify(x<<1, l, mid, L, R, pos);
if(R>mid) modify(x<<1|1, mid+1, r, L, R, pos);
}
bool topsort(){
queue<int> q;
for(int i = 1; i <= node; i++) if(!du[i]) q.push(i);
int cnt = 0;
while(!q.empty()){
int x = q.front(); q.pop(); ++cnt;
if(val[x] > 1e9) return false;
if(a[x] && val[x] > a[x]) return false;
val[x] = max(1, max(val[x], a[x]));
for(int i = first[x]; i; i = nxt[i]){
int t = to[i]; val[t] = max(val[t], val[x] + w[i]);
if(--du[t] == 0) q.push(t);
}
} return cnt >= node; // circle
}
int main(){
// freopen("web.in","r",stdin);
// freopen("web.out","w",stdout);
n = node = read(), s = read(), m = read();
for(int i = 1; i <= s; i++){
int p = read(); a[p] = read();
} build(1, 1, n);
while(m--){
int l = read(), r = read(), k = read();
int pre = l - 1;
int fake = ++node; // fake position
for(int i = 1; i <= k; i++){
int pos = read();
add(fake, pos, 0);
if(pre + 1 <= pos - 1) modify(1, 1, n, pre + 1, pos - 1, fake);
pre = pos;
}
if(pre + 1 <= r) modify(1, 1, n, pre + 1, r, fake);
}
if(!topsort()) puts("Impossible");
else{
puts("Possible");
for(int i = 1; i <= n; i++) cout << val[i] << " ";
} return 0;
}