【HDU 5772】String problem

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5772
神犇题解:http://www.cnblogs.com/qscqesze/p/5715939.html

解题报告

日常看错题系列:

  1. 子序列看成子串
  2. 值域范围$0 \sim 9$看漏

于是只会跑$n$遍网络流的算法了 QwQ
大概就是枚举左端点,然后暴力跑

考虑原题的话,唯一的Trick就是如何处理一个数第一次选的代价不同
我们可以使用最大权闭合子图来解决这个问题:

每一类数字新建一个结点,权值为$b_i – a_i$
值为该数字的所有序列上的点向这个新建的点连一条有向边

至于本题的其他部分就没什么好玩的了

【HDU 4349】Xiao Ming’s Hope

相关链接

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4349
神犇题解:http://blog.csdn.net/u013486414/article/details/48130553

题目大意

求${n \choose i},i \in [1,n]$中有多少个奇数
其中$n \le 10^{18}$

解题报告

原题相当于求${n \choose i} \% 2$有多少个为$1$
考虑使用$Lucas$定理,将模数设为$2$
此时相当于把$n,i$都转成了二进制下,然后单独考虑每一位
因为${1 \choose 1} = {1 \choose 0} = {0 \choose 0} = 1,{0 \choose 1} = 0$
所以当$n$的那个二进制位为$1$的时候,$i$那一位可以为$0/1$,但当$n$那一位为$0$时,$i$只能为$0$
所以最终方案数为$2^{\sum\limits_{i=0}^{63}{(n>>i) \& 1}}$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int main() {
	for (LL n,ans;~scanf("%I64d",&n);){
		ans = pow(2, __builtin_popcountll(n)) + 0.5;
		cout<<ans<<endl;
	}
	return 0;
}

【BZOJ 1937】[SHOI2004] Mst最小生成树

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1937
神犇题解:https://blog.sengxian.com/solutions/bzoj-1937

解题报告

我们首先可以得到一个结论:$T$中的边的边权只会减少,其他边的边权只会增加
于是我们将$T$中的边放在左边,其他边放在右边,都作为二分图的点,点权为边权
然后左右两两连边,边权$v_i$为右边点的权值减左边点的权值

此时问题转化为,每一个点设一个$d_i$,满足二分图中任意一条边$i \to j$满足$v_{i \to j} \le d_i + d_j$,求最小化$\sum{d_i}$
这是$KM$算法的关键步骤。于是直接上$KM$算法就可以了

但我不会$KM$算法
←为什么这个频率这个鬼畜啊 QwQ

但不会$KM$算法我们也能做,回忆$KM$算法顶标的作用
最小的顶标和就是最大权匹配的权值和
于是我们用费用流增广这个二分图,直到增广到权值为负就可以了

【BZOJ 3514】Codechef MARCH14 GERALD07加强版

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3514
神犇题解:http://www.cnblogs.com/zhonghaoxi/p/3651591.html

解题报告

这是LCT的经典应用,我们把每条边的权值设为它的编号
然后用LCT动态维护最大生成树
同时记录第$i$条边加入时取代的那一条边的编号$a_i$

对于询问,我们发现对于询问$[l,r]$
只有$a_i < l$的边才会使连通块的大小减少$1$
于是问题变为询问一个区间内小于某个数的个数
这是又是函数式线段树的经典应用

于是这题用LCT和函数式线段树就可以解决
时间复杂度:$O(n \log n)$

相关拓展

这题还可以很方便地将问题改为删掉$[l,r]$的边之后连通块的个数
这个我们将序列倍长,然后直接转化为原问题

【CodeChef PARITREE】[MARCH16] Parity tree

相关链接

题目传送门:https://www.codechef.com/MARCH16/problems/PARITREE
神犇题解:http://r64.is-programmer.com/posts/197030.html

解题报告

这题大概是PA2014 Kuglarz的升级版
我们发现其给定树上一条路径的奇偶性,实际上可以理解为给定路径上两个端点到根的路径的奇偶性的关系
换一句话来说,这两个的奇偶性定了一个,另一个也就确定了

所以给定$u,v$的奇偶性,就用并查集把这两货连一起
最后假设有$k$个连通块,那答案就是$2^k$
当然我们还需要特判一下无解的情况,在并查集那里多记录一点东西就可以了

【BZOJ 3566】[SHOI2014] 概率充电器

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3566
神犇题解:https://blog.sengxian.com/solutions/bzoj-3566

解题报告

首先根据期望的线性,我们只需要求出每个元件有电的概率,之后相加就是答案
那么怎么求每个元件有电的概率呢?我们可以无脑点分
显然在$O(n)$的时间复杂度内搞一发树形$DP$就可以了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 500009;
const int M = N << 1;

int n,head[N],nxt[M],to[M]; 
double up[N],down[N],cost[M],q[N],vout;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline void AddEdge(int u, int v, int f) {
	static int E = 1; 
	cost[E+1] = cost[E+2] = f / 100.0;
	to[++E] = v; nxt[E] = head[u]; head[u] = E;
	to[++E] = u; nxt[E] = head[v]; head[v] = E;
}

void DFS1(int w, int f) {
	down[w] = 1 - q[w];
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != f) {
			DFS1(to[i], w);
			down[w] *= down[to[i]] + (1 - down[to[i]]) * (1 - cost[i]);
		}
	}
}

void DFS2(int w, int f, double p) {
	vout += 1 - (up[w] = down[w] * p);
	for (int i=head[w];i;i=nxt[i]) {
		if (to[i] != f) {
			double tmp = p * down[w] / (down[to[i]] + (1 - down[to[i]]) * (1 - cost[i]));
			DFS2(to[i], w, tmp + (1 - tmp) * (1 - cost[i]));
		}
	}
}

int main() {
	n = read();
	for (int i=1,u,v;i<n;i++) {
		u = read(); v = read();
		AddEdge(u, v, read());
	}
	for (int i=1;i<=n;i++) q[i] = read() / 100.0;
	DFS1(1, 1);
	DFS2(1, 1, 1);
	printf("%.6lf\n",vout);
	return 0;
}

【BZOJ 2521】[SHOI2010] 最小生成树

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2521
神犇题解:http://krydom.com/bzoj2521/

解题报告

这题有一个非常显然的贪心:做Kruskal的时候,如果会使那条边的两个端点连在一起就暴力加权值
但这样显然也是错误的,因为我们可能是在之前的步骤就废掉一些边
于是怎么解决这个问题呢?
我们相当于不能在某两个端点之间有通路,那么这和网络流的增广路相同
于是我们把边的容量设为暴力改的时候需要的花费,然后跑一遍最小割就可以了

【BZOJ 3517】翻硬币

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3517
神犇题解Ⅰ:http://blog.csdn.net/lych_cys/article/details/50363106
神犇题解Ⅱ:http://foreseeable97.logdown.com/posts/193171-bzoj3517-coins

解题报告

一道非常妙妙的题目啊!虽然没做出来 QwQ

我们先来明确两个结论:
1. 首先一个格子要么被翻一次,要么不被翻
2. 其次全部变白和全部变黑是互逆的,所以求出一个可以推出另一个。所以我们只考虑求全部变白的情况

我们考虑设$b_{i,j}$表示最终对没对$(i,j)$这个格子进行操作
于是我们可以得到$n^2$个方程,因为只有$n^2$个变量
所以我们可以用高斯消元在$O(n^3)$的时间复杂度内求解

但我我们发现题目中提到的$n$为偶数这个条件并没有用到
现在考虑优化高斯消元:

设$a_{i,j}$为这个格子的原始状态
设$xb_i$表示第$i$行所有$b_{i,j}$异或起来,$xa_i$表示第$i$行所有$a_{i,j}$异或起来
设$yb_i$表示第$i$列所有$b_{i,j}$异或起来,$ya_i$表示第$i$列所有$a_{i,j}$异或起来
于是我们可以得到$b_{i,j} \oplus xb_j \oplus yb_i = a_{i,j}$,设为一号方程
考虑我们求$b_{i,j}$,那么我们把所有第$j$行和第$i$列的方格对应的一号方程异或起来
之后我们发现因为$n$为偶数,所以等式变为$b_{i,j}=ya_i \oplus xa_j \oplus a_{i,j}$

然后我们惊讶地发现问题已经解决了?
第一次手动解异或方程组,感觉非常玄妙啊!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 1009;

int n,ans,x[N],y[N],a[N][N];
char pat[N]; 

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	n = read();
	for (int j=1;j<=n;j++) {
		scanf("%s",pat+1);
		for (int i=1;i<=n;i++) {
			a[i][j] = pat[i] - '0';
			x[i] ^= a[i][j];
			y[j] ^= a[i][j];
		}
	}
	for (int j=1;j<=n;j++) {
		for (int i=1;i<=n;i++) {
			ans += x[i] ^ y[j] ^ a[i][j];	
		}
	} 
	printf("%d\n",min(ans, n * n - ans));
	return 0;
}

【BZOJ 3551】[ONTAK2010] Peaks加强版

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3551
神犇题解:http://blog.csdn.net/popoqqq/article/details/41348785

解题报告

这题强制在线,所以不能直接上启发式合并
或者强行可持久化也可以?
不过我们有更加优美的东西:Kruskal重构树

就是按照边权排序,然后做Kruskal
如果需要加边,那么我们新建一个结点,权值设为这条边的权值
然后把原来的两个点连到这个点下面当儿子
于是任意两点之间的最大边权就是他们的LCA的权值了

对于原题来讲,我们可以先倍增到最浅的可以到达的祖先
那么这个祖先的子树就是可以到达的所有点了
考虑DFS序,问题变为区间k大,这是主席树的经典应用
于是这题就做完啦!

今天上午考试考了一道类似的题目,然而忘了这个做法
是用的线段树合并+标记永久化,虽然能A但显然不如这个优雅

【日常小测】cut

题目大意

给定一个$n(n \le 100)$个点的连通图
不同顶点之间有$n^2$个最小割
现在给定这$n^2$个最小割,让你构造一个图使其满足条件

解题报告

据说这是论文题?

因为任意两点间的最小割可以用最小割树来表示
所以我们考虑构造一个树形结构

因为一条路径上的最小割相当于取$min$,所以流量大的不会影响流量小的
所以我们先按边权从大到小排序
如果当前这条边的两个端点已经在一个连通块内,那么暴力判断是否符合要求
如果不在一个块内,那么连一条边

至于为什么这样是对的,因为已经按权值排序了
如果不在一个连通块内,还不连边,那么最终连通这两块的权值一定大于要求的最小割,所以必须立刻连边
至于已经连边了还冲突的话,因为之前的边都是必要的,所以也没有调整的需要,一定是无解的

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 109;
const int M = N * N << 1;
const int INF = 1e9;
 
int n,tot,head[N],nxt[M],to[M],cost[M],fa[N];
struct Data{
    int u,v,val;
    inline Data() {}
    inline Data(int a, int b, int c):u(a),v(b),val(c) {}
    inline bool operator < (const Data &B) const {
        return val > B.val;
    }
}p[N*N*N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret * f;
}
 
inline void AddEdge(int u, int v, int c) {
    static int E = 1; cost[E+1] = cost[E+2] = c; 
    to[++E] = v; nxt[E] = head[u]; head[u] = E;
    to[++E] = u; nxt[E] = head[v]; head[v] = E;
}
 
int cal(int w, int f, int pur, int mn) {
    if (w == pur) return mn;
    for (int i=head[w],tmp;i;i=nxt[i]) {
        if (to[i] != f) {
            tmp = cal(to[i], w, pur, min(mn, cost[i]));
            if (~tmp) return tmp;
        }
    } return -1;
}
 
int find(int x) {return x==fa[x]? x: fa[x]=find(fa[x]);}
 
int main() {
    n = read();
    for (int i=1,v;i<=n;i++) {
        fa[i] = i;
        for (int j=1;j<=n;j++) {
            if (v=read(), i==j) continue;
            p[++tot] = Data(i, j, v);   
        }
    }
    sort(p+1, p+1+tot);
    for (int i=1;i<=tot;i++) {
        int u=p[i].u, v=p[i].v, val=p[i].val;
        if (find(u) == find(v)) {if(cal(u,u,v,INF)!=val)cout<<-1,exit(0);}
        else fa[find(u)] = fa[v], AddEdge(u, v, val);
    }
    cout<<n-1<<endl;
    for (int i=2;to[i];i+=2) printf("%d %d %d\n",to[i],to[i+1],cost[i]);
    return 0;
}

【日常小测】Mortal Kombat

题目大意

给定$n(n \le 300)$个外星人,给定$m(m \le 1500)$个地球人,再给定任意地球人与外星人的关系
关系由一个$nm$的0/1矩阵给出,表示第$i$个外星人是否能与第$j$个地球人配对
要求每个外星人至少与一个地球人配对,地球人只能与至多一个外星人匹配
问由哪些关系一定不可能出现在合法的匹配方案中

解题报告

这是一道基础图论题

我们可以先来考虑一个简单的版本:地球人个数与外星人一样
那么我们可以先用二分图匹配跑一个完备匹配出来
此时在匹配中的边一定可以出现在合法的匹配方案中

只考虑不在当前匹配中的边,那么一定是走一条像增广路一样的东西
就是一条匹配边,一条非匹配边交叉着走。最后走一个偶环绕回来
于是我们对于从左边连到右边的边,只保留非匹配边
对于从右到左的边,我们只保留匹配边。这样就可以保证是偶环了
至于能不能走回来,我们发现这就是一个有向图的强连通分量
于是我们再跑一个Tarjan,判断一下就可以了

现在考虑地球人与外星人不等的情况
我们可以把外星人补到与地球人一样多啊!
就是加一些可以与所有地球人匹配的外星人就可以了
当然不加点,大力讨论一波也是可以的

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
  
const int N = 3009;
const int M = N * N << 1;
const int INF = 1e9;
  
int head[N],nxt[M],to[M],flow[M],low[N],dfs[N];
int n,m,S,T,E=1,mth[N],id[N][N],num[N],ins[N]; char pat[N]; 
stack<int> stk;
  
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret * f;
}
  
inline int AddEdge(int u, int v, int f=0) {
    if (f) {
        to[++E] = v; nxt[E] = head[u]; head[u] = E;
        to[++E] = u, nxt[E] = head[v], head[v] = E;
        flow[E - 1] = f; return E - 1;
    } else to[++E] = v, nxt[E] = head[u], head[u] = E;
}
  
class Network_Flow{
    int dis[N],cur[N]; queue<int> que;
    public:
        inline int MaxFlow() {
            int ret = 0;
            while (BFS()) {
                memcpy(cur,head,sizeof(cur));
                ret += DFS(S, INF);
            } return ret;
        }
    private:
        int DFS(int w, int f) {
            if (w == T) return f; int ret = 0;
            for (int &i=cur[w],tmp;i;i=nxt[i]) {
                if (flow[i] && dis[to[i]] == dis[w] + 1) {
                    tmp = DFS(to[i], min(f, flow[i]));
                    flow[i] -= tmp; flow[i^1] += tmp;
                    f -= tmp; ret += tmp;
                    if (!f) return ret;
                }
            } return ret;
        }
        inline bool BFS() {
            memset(dis,60,sizeof(dis));
            dis[S] = 0; que.push(S);
            while (!que.empty()) {
                int w = que.front(); que.pop();
                for (int i=head[w];i;i=nxt[i]) {
                    if (flow[i] && dis[to[i]] > INF) {
                        dis[to[i]] = dis[w] + 1; 
                        que.push(to[i]);
                    }
                }
            } return dis[T] < INF;
        }
}Dinic;
 
void Tarjan(int w) { 
    static int dfs_cnt = 0, scc_cnt = 0; 
    dfs[w] = low[w] = ++dfs_cnt; stk.push(w); ins[w] = 1;
    for (int i=head[w];i;i=nxt[i]) {
        if (!dfs[to[i]]) Tarjan(to[i]), low[w] = min(low[w], low[to[i]]);
        else if (ins[to[i]]) low[w] = min(low[w], dfs[to[i]]);  
    }
    if (low[w] == dfs[w]) {
        for (scc_cnt++;!stk.empty();stk.pop()) {
            num[stk.top()] = scc_cnt; ins[stk.top()] = 0;
            if (stk.top() == w) {stk.pop(); break;}
        }
    }
}
 
int main() {
    n = read(); m = read(); S = 0; T = N - 1;
    memset(id, -1, sizeof(id));
    for (int i=1;i<=n;i++) {
        scanf("%s",pat+1);
        for (int j=1;j<=m;j++) {
            if (pat[j] == '1') {
                id[i][j] = AddEdge(i, m + j, 1);
            }   
        }
    } 
    for (int i=1;i<=n;i++) AddEdge(S, i, 1);
    for (int i=1;i<=m;i++) AddEdge(m + i, T, 1);
    if (Dinic.MaxFlow() != n) {
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=m;j++) putchar('1');
            puts("");
        }
    } else {
        E = 1; memset(head,0,sizeof(head));
        for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) 
            if (~id[i][j] && !flow[id[i][j]]) {mth[j] = i; break;}
        for (int i=n+1,j=1;i<=m;i++,j++) {while(mth[j])j++;mth[j]=i;}
        for (int i=1;i<=m;i++) for (int j=1;j<=m;j++) if (~id[i][j] || i > n) {
            if (mth[j] == i) AddEdge(m+j, i); else AddEdge(i, m+j);}
        for (int i=1;i<=m*2;i++) if (!dfs[i]) Tarjan(i);
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=m;j++) {
                if ((~id[i][j]) && (mth[j] == i || num[i] == num[m+j])) putchar('0');
                else putchar('1');
            } putchar('\n');
        }
    }
    return 0;
}

【BZOJ 3897】Power

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3897
神犇题解:http://blog.csdn.net/jiangyuze831/article/details/44701467

解题报告

我们考虑给定一个区间$[l,r]$的起始体力值和最终体力值
那么显然我们应该把尽量多的体力值分配到收益最大的那一天$i$
然后$(i,r]$那一部分成了递归子问题,直接分治下去就可以了

至于前面那一部分,如果一直蓄力都不会满,那么就一直蓄力
否则的话,假设从第$j$天开始蓄力会满掉那么我们递归$[l,j-1]$即可
于是我们撸一发分治就可以了,时间复杂度:$O(n)$

【BZOJ 3638】[CF172] k-Maximum Subsequence Sum

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3638
神犇题解Ⅰ:http://blog.csdn.net/werkeytom_ftd/article/details/50950623
神犇题解Ⅱ:http://hzwer.com/6715.html

解题报告

这题非常的妙啊!
但我还是点亮手动增广这个技能点 QwQ

考虑单次询问整个区间,我们建一个费用流的图就可以辣!
然后我们发现这个费用流的每次增广相当于区间取反+区间询问最大连续字段和
这个是线段树的经典问题,于是我们用线段树来模拟增广的过程就可以辣!

【日常小测】tree

题目大意

给定一棵大小为$n(n \le 3000)$的树,带边权
请你给出一个长度为$k(k \le n)$的序列$\{a_i\}$
要求序列中两两元素不同,请你最小化$\sum\limits_{i=1}^{k-1}{dis(a_i,a_{i+1})}$

解题报告

我们看一看这个式子可以发现实际上是要我们给出一个大小为$k$的连通块
问你这个连通块内的边权乘上$2$再减掉直径的最小值是多少

于是我们定义$h_{i,j}$为$i$的子树中选$j$个点,还没有考虑直径的最小花费
$f_{i,j}$表示在$i$的子树里,选$j$个点,直径包含$i$的最小花费
$g_{i,j}$表示在$i$的子树里,选$j$个点,已经考虑过直径且直径不包含$i$的最小花费
然后我们暴力$DP$就可以了

我们的时间复杂度是$T(n)=\sum\limits_{i}{\sum\limits_{fa_i=fa_j}{size_i \cdot size_j}}$
仔细想一想的话,每一堆点只会在$LCA$处被计算,时间复杂度是$O(n^2)$的

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 3009;
const int M = N << 1;
const LL INF = 1e18;

int n,k,head[N],nxt[M],to[M],cost[M],sz[N];
LL vout=INF,f[N][N],g[N][N],h[N][N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline void AddEdge(int u, int v, int c) {
	static int E = 1; cost[E+1] = cost[E+2] = c;
	to[++E] = v; nxt[E] = head[u]; head[u] = E;
	to[++E] = u; nxt[E] = head[v]; head[v] = E;
}

void update(int w, int fa) { 
	fill(f[w], f[w]+1+N, INF);
    fill(g[w], g[w]+1+N, INF);
	fill(h[w], h[w]+1+N, INF);
	f[w][1] = g[w][1] = h[w][1] = 0; sz[w] = 1;
	for (int i=head[w],t;i;i=nxt[i]) {
		if ((t=to[i]) != fa) {
			update(to[i], w); 
			for (int x=sz[w],tmp=cost[i]<<1;x;x--) {
				for (int j=1;j<=sz[t];j++) {
					g[w][x+j] = min(g[w][x+j], g[w][x] + h[t][j] + tmp);
					g[w][x+j] = min(g[w][x+j], f[w][x] + f[t][j] + cost[i]);
					g[w][x+j] = min(g[w][x+j], h[w][x] + g[t][j] + tmp);
				}
			}
			for (int x=sz[w],tmp=cost[i]<<1;x;x--) {
				for (int j=1;j<=sz[t];j++) {
					f[w][x+j] = min(f[w][x+j], h[w][x] + f[t][j] + cost[i]);
					f[w][x+j] = min(f[w][x+j], f[w][x] + h[t][j] + tmp);
				}
			} 
			for (int x=sz[w],tmp=cost[i]<<1;x;x--) {
				for (int j=1;j<=sz[t];j++) 
					h[w][x+j] = min(h[w][x+j], h[w][x] + h[t][j] + tmp);
			} 
			sz[w] += sz[t];
		}	
	}
	vout = min(vout, f[w][k]);
	vout = min(vout, g[w][k]); 
}

int main() {
	n = read(); k = read();
	for (int i=2,u,v;i<=n;i++) {
		u = read(); v = read();
		AddEdge(u, v, read());
	}
	update(1, 1);
	printf("%lld\n",vout);
	return 0;
}

【日常小测】Different Trips

题目大意

给定一棵$n(n \le 10^5)$个结点的树,每一个结点的特征值定义为这个结点的度
询问从根节点走到叶子结点的所有路径中本质不同的字符串的数量

解题报告

这题就是诸神眷顾的幻想乡的弱化版,撸一发广义SAM就可以了

不过GEOTCBRL考完之后给我们增加了一点姿势水平:

广义后缀自动机如果使用DFS建立的话,时间复杂度是$O($叶子结点深度和$)$的
如果使用BFS来建立的话,时间复杂度是$O(n \cdot$字符集大小$)$

这在15年刘研绎的集训队论文《后缀自动机在字典树上的拓展》中有详细的证明
其中还给出了构造极端数据的构造方法
所以这题字符集大小这么大的话,这样会T的
可能只有后缀数组才能做 QwQ

Code

这辣鸡代码是可以卡的,泥萌不要卡啊…….

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 2000009;
 
int n,head[N],nxt[N],to[N],in[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline void AddEdge(int u, int v) {
    static int E = 1; in[u]++; in[v]++;
    to[++E] = v; nxt[E] = head[u]; head[u] = E;
    to[++E] = u; nxt[E] = head[v]; head[v] = E;
}
 
class Suffix_Automaton{
    int tot,len[N],fail[N],vis;
    map<int,int> ch[N];
    public:
        inline Suffix_Automaton() {tot=1;}
        inline int insert(int t, int c) {
            if (ch[t]&&len[ch[t]]==len[t]+1) return ch[t];
            int w = ++tot; len[w] = len[t] + 1;
            for (;t&&!ch[t];t=fail[t]) ch[t] = w;
            if (!t) fail[w] = 1;
            else if (len[ch[t]] == len[t] + 1) fail[w] = ch[t];
            else {
                int nt = ++tot, pt = ch[t];
                ch[nt] = ch[pt]; len[nt] = len[t] + 1;
                fail[nt] = fail[pt]; fail[pt] = fail[w] = nt;
                for (;t&&ch[t]==pt;t=fail[t]) ch[t] = nt;
            } return w;
        }
        inline LL GetAns() {
            LL ret = 0;
            for (int i=2;i<=tot;i++)
                ret += len[i] - len[fail[i]];
            return ret;
        }
}SAM;
 
void DFS(int w, int f, int ins) {
    int nins = SAM.insert(ins, in[w]);
    for (int i=head[w];i;i=nxt[i]) {
        if (to[i] != f) {
            DFS(to[i], w, nins);
        }
    }
}
 
int main() {
    n = read();
    for (int i=1;i<n;i++) AddEdge(read(), read());
    DFS(1, 1, 1);
    printf("%lld\n",SAM.GetAns());
    return 0;
}