poj 1236
题意:给你一些有向边,然后求至少给几个学校发消息,才能让所有学校都获得消息,还有个问题是需要添多少条边,才能让这个变成连通图
题解:用tarjan缩点,然后算每个连通分量的入度和出度
第一个问题的答案就是入度为0的个数
第二个问题是入度为0和出度为0的个数的最大值
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define MAX 105 #define MAXN 100005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const int mod = 1000000007; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; //读入外挂 inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } struct Edge{ int v,next; }edge[MAX*MAX]; int head[MAX]; int DFN[MAX];//记录搜到的时间序号 int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号 int instack[MAX];//某个元素是否在栈中 int sstack[MAX];//模拟栈 int belong[MAX]; int in[MAX]; int out[MAX]; int index; int tot; int top; int cnt; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void tarjan(int u){ DFN[u]=low[u]=++index; instack[u]=1; sstack[++top]=u; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!DFN[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(DFN[v],low[u]); } if(DFN[u]==low[u]){ cnt++; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; if(k==u) break; } } } int main(){ int n; scanf("%d",&n); mem1(head); mem0(instack); mem0(DFN); mem0(in); mem0(out); tot=0; top=0; cnt=0; index=0; for(int i=1;i<=n;i++){ int a; while(scanf("%d",&a)&&a){ add_edge(i,a); } } for(int i=1;i<=n;i++){ if(!DFN[i]) tarjan(i); } for(int i=1;i<=n;i++){ for(int j=head[i];j!=-1;j=edge[j].next){ int v=edge[j].v; if(belong[i]==belong[v]) continue; in[belong[v]]++; out[belong[i]]++; } } int innum=0; int outnum=0; for(int i=1;i<=cnt;i++){ if(in[i]==0) innum++; if(out[i]==0) outnum++; } if(cnt==1) printf("1\n0\n"); else{ printf("%d\n",innum); printf("%d\n",max(innum,outnum)); } return 0; }poj 1144
题意:给你一个无向连通图,求几个点,如果去掉这个点,图就不连通了
题解:这是求割点的模板题,就是输入特别hentai
求割点的思想就用在tarjan上修改,如果是根节点,他有二个儿子或者以上,那么就是割点,如果一个点不是根节点,往下深搜,如果他的子节点的low大于等于他的DFN
(DFN记录这个点到达的时间,low记录这个点或者他的儿子到达栈中的元素的DFN),DFN[u]<=low[v]的意思就是v点只能达到还未搜过的点或者是父节点,意思就是去掉这个u,他们就不连通了。
终于获得了神器——王尼玛的图论模板(感觉逼格暴增,不过仍需努力)
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define MAX 105 #define MAXN 100005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const int mod = 1000000007; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; //读入外挂 inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } struct Edge{ int v,next; }edge[MAX*MAX]; int head[MAX]; int DFN[MAX];//记录搜到的时间序号 int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号 int vis[MAX]; int root; int index; int tot; int tmp; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void tarjan(int u){ DFN[u]=low[u]=++index; vis[u]=1; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!vis[v]){ tarjan(v); low[u]=min(low[u],low[v]); if(DFN[u]<=low[v]) vis[u]++; } else low[u]=min(DFN[v],low[u]); } if(u==root&&vis[u]>2||u!=root&&vis[u]>1) tmp++; } int main(){ int n; while(scanf("%d",&n)&&n){ int u,v; mem0(DFN); mem0(low); mem0(vis); mem1(head); tot=0; root=1; index=0; tmp=0; while(scanf("%d",&u)&&u){ while(getchar()!='\n'){ scanf("%d",&v); add_edge(u,v); add_edge(v,u); } } for(int i=1;i<=n;i++){ if(!DFN[i]) tarjan(i); } printf("%d\n",tmp); } return 0; }
poj 2117
题意:给你n个发电厂,然后m条边,(无向图),问你拆掉一个发电厂,如何让连通块最多
题解:肯定是拆割点啦,就是算每个割点和多少个儿子连着(根和非根要分开算,还要用tarjan算出一开始有多少个连通块)另外要注意trick,如果特殊情况割点为0的时候:如果边是0的话那么就是连通块个数-1,如果有边的话那么就是连通块的个数啊
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define MAX 10005 #define MAXN 100005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const int mod = 1000000007; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; //读入外挂 inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } struct Edge{ int v,next; }edge[50000000]; int head[MAX]; int DFN[MAX];//记录搜到的时间序号 int low[MAX];//记录该点往下搜,能搜到的栈中最小的时间序号 int vis[MAX]; int instack[MAX]; int sstack[MAX]; int Index; int tot; int tmp; int cnt; int top; void add_edge(int a,int b){ edge[tot].v=b; edge[tot].next=head[a]; head[a]=tot++; } void tarjan(int u,int fa){ DFN[u]=low[u]=++Index; vis[u]=1; instack[u]=1; sstack[++top]=u; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!vis[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); if(DFN[u]<=low[v]) vis[u]++; } else if(instack[v])low[u]=min(DFN[v],low[u]); } if(fa==-1&&vis[u]>2) tmp=max(tmp,vis[u]-2); if(fa!=-1&&vis[u]>1) tmp=max(tmp,vis[u]-1); if(DFN[u]==low[u]){ cnt++; while(1){ int k=sstack[top--]; instack[k]=0; if(k==u) break; } } } int main(){ int n,m; while(scanf("%d%d",&n,&m)&&n){ int u,v; mem0(DFN); mem0(low); mem0(vis); mem1(head); mem0(instack); tot=0; Index=0; tmp=0; top=0; cnt=0; for(int i=0;i<m;i++){ scanf("%d%d",&u,&v); add_edge(u,v); add_edge(v,u); } for(int i=0;i<n;i++){ if(!DFN[i]) tarjan(i,-1); } if(tmp==0){ if(m==0) printf("%d\n",cnt-1); else printf("%d\n",cnt); } else printf("%d\n",tmp+cnt); } return 0; }
HDU 4612
题意:给你一个无向图,里面有桥,问你连给它加一条边,桥变为多少
题解:很明显是先求出桥的数量,然后缩点成一棵树,然后求树的直径,答案就是桥-直径
但是这题有20W点100W边,而且有重边,我重边处理的不太好,一直不太会,特别是20W点,我都不知道如何记录,看了题解之后学了个比较屌的方法,而且可以一边求桥一边用树形dp求直径,而不需要缩点后用连通分量做,代码顿时短了很多
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 200005 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; struct Edge{ int v,next; }edge[MAXN]; int head[MAX]; int dfn[MAX]; int low[MAX]; int vis[MAXN]; int dp[MAX][2];//0存的是点u往下的最长路径,1存的是u往下的次长路径 int tot; int Index; int bridge; void init(){ mem1(head); mem0(dfn); mem0(low); mem0(vis); tot=0; Index=0; bridge=0; } void add_edge(int a,int b){ edge[tot].v=b; edge[tot].next=head[a]; head[a]=tot++; } void tarjan(int u){ dfn[u]=low[u]=++Index; dp[u][0]=dp[u][1]=0; for(int i=head[u];i!=-1;i=edge[i].next){ if(!vis[i>>1]){ vis[i>>1]=1; int v=edge[i].v; if(!dfn[v]){ tarjan(v); int temp=dp[v][0];//树形dp if(dfn[u]<low[v]){ bridge++; temp++;//是否在同一个连通块里 } if(temp>dp[u][0]){ dp[u][1]=dp[u][0]; dp[u][0]=temp; } else if(dp[u][1]<temp) dp[u][1]=temp; low[u]=min(low[v],low[u]); } else low[u]=min(dfn[v],low[u]); } } } int main(){ int n,m; while(scanf("%d%d",&n,&m)&&n&&m){ init(); for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } tarjan(1); int ans=0; for(int i=1;i<=n;i++){ ans=max(ans,dp[i][0]+dp[i][1]); } printf("%d\n",bridge-ans); } return 0; }
hdu 4607
题意:这题给你一棵树,然后要游玩k个点,问你最少走多少距离
题解:这题很明显是和树的直径有关,但是我比较虚,k>直径时怎么弄有点犯二
现在想想挺简单的,比如a,b是直径的起点终点,然后k>直径上的点,那么就从a开始走,走到一个节点,就往不和直径重合的路径上走,直径上有d个点,那么要走的不在直径上的点就有k-d个,那么从a,b之间一些节点去绕路走这k-d个点,最少需要走2*(k-d)的距离(想上去还是挺明显的),然后直径长度d-1。
所以结论是k<=d ,ans=k-1
k>d, ans=d-1+2*(k-d);
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 200005 #define MAXN 2000005 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const LL mod = 1000000; const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; struct Edge{ int v,next; }edge[MAXN]; int head[MAX]; int dp[MAX][2]; int vis[MAX]; int tot; int d; void init(){ mem1(head); mem0(vis); tot=0; d=0; } void add_edge(int a,int b){ edge[tot].v=b; edge[tot].next=head[a]; head[a]=tot++; } void tree_dp(int u){//树形dp vis[u]=1; dp[u][0]=dp[u][1]=0; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!vis[v]){ tree_dp(v); int temp=dp[v][0]+1; if(temp>dp[u][0]){ dp[u][1]=dp[u][0]; dp[u][0]=temp; } else if(temp>dp[u][1]) dp[u][1]=temp; } } d=max(d,dp[u][0]+dp[u][1]); } int main(){ int t; scanf("%d",&t); while(t--){ init(); int n,m; scanf("%d%d",&n,&m); for(int i=0;i<n-1;i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } tree_dp(1); d++; while(m--){ int k; scanf("%d",&k); if(k<=d) printf("%d\n",k-1); else printf("%d\n",d-1+2*(k-d)); } } return 0; }
hdu 2243
题意:给你n个字符串,问你长度小于等于m的字符串中必须出现这n个其中一个子串的字符串有多少情况
题解:前面做过一定不能出现,现在是反过来考虑,所以先考虑所有字符串,就是从26^1+26^2+......+26^m,这个用快速幂+递归求等比数列就好了
然后是计算长度小于等于m的字符串中不能出现上述字符串的个数,两个相减就是结果
由于是模1<<64,直接用unsigned long long即可,会自动模
难点就在于这个n个字符串组成的矩阵
首先是构造AC自动机,然后如果val[j],last[j]不为0的话就continue,为0的地方就在矩阵上++(可以到达)
然后就是求A^1+A^2+……+A^m这个矩阵的第一行的和
如果用快速幂+递归很有可能爆掉
矩阵是一种十分奇妙的东西,假如矩阵A是n行n列,年神教我开成n+1行n+1列,最后一行都是0,最后一列都是1(A[n+1][n+1]=1)
这样的话两个矩阵相乘,第一行的前n个,还是和n*n的两个A相乘一样,第一行的最后一个值,就是n+1个1和另外个A的第一行相乘,值是A的第一行的值+1
以此类推,第二次得到的最后一个值就是A^2+A的第一行的值+1,最后得到的就是A^m-1+……+1,然后只要把最后得到的矩阵的第一行相加即可
如此巧妙的技巧我必须以后多加练习,矩阵的优化是十分的奇妙
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 100005 #define MAXN 2000005 #define maxnode 110 #define sigma_size 26 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem0(x) memset(x,0,sizeof(x)) #define mem1(x) memset(x,-1,sizeof(x)) #define meminf(x) memset(x,INF,sizeof(x)) #define lowbit(x) (x&-x) const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const LL mod = (1<<64); /**************¶áèëía1ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct AC{ int ch[maxnode][sigma_size]; int val[maxnode], last[maxnode], f[maxnode]; int sz; void init(){ mem0(ch[0]); last[0]=val[0]=0; sz = 1; } int idx(char c){return c-'a';} void insert(const char *s,int v){ int n = strlen(s), u =0; for(int i=0;i<n;i++){ int c = idx(s[i]); if(c==-1) return ; if(!ch[u][c]){ mem0(ch[sz]); val[sz]=0; ch[u][c]=sz++; } u = ch[u][c]; } val[u]=v; } void bfs(){ queue<int>q; f[0]=0; for(int c = 0; c<sigma_size;c++){ int u = ch[0][c]; if(u){q.push(u); f[u]=last[u]=0;} } while(!q.empty()){ int r =q.front(); q.pop(); for(int c = 0;c<sigma_size;c++){ int u = ch[r][c]; if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句 q.push(u); int v = f[r]; while(v && !ch[v][c]) v = f[v]; f[u] = ch[v][c]; last[u] = val[f[u]] ? f[u] : last[f[u]]; } } } }ac; struct Matrix{ int n; ull maze[maxnode][maxnode]; void init(int n){ this->n = n; mem0(maze); } Matrix operator * (Matrix &rhs){ Matrix m; m.init(n); for(int i=0;i<n;i++) for(int j=0;j<n;j++) for(int k=0;k<n;k++) m.maze[i][j] = m.maze[i][j] + maze[i][k] * rhs.maze[k][j]; return m; } Matrix operator + (Matrix &rhs){ Matrix m; m.init(n); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ m.maze[i][j] = maze[i][j] + rhs.maze[i][j]; } } return m; } }; ull qpow(Matrix a,int n){ Matrix ans; ans.init(a.n); for(int i=0;i<ans.n;i++) ans.maze[i][i] = 1; while(n){ if(n&1) ans = ans * a; a = a * a; n >>= 1; } ull tmp=0; for(int i=0;i<ans.n;i++) tmp+=ans.maze[0][i]; return tmp; } ull ppow(ull a,int m){ ull ans=1; ull b=a; while(m){ if(m&1) ans*=b; b*=b; m>>=1; } return ans; } char s[20]; int main(){ int n,m; while(~scanf("%d%d",&n,&m)){ ac.init(); for(int i=0;i<n;i++){ scanf("%s",s); ac.insert(s,1); } ac.bfs(); Matrix A; A.init(ac.sz+1); for(int i=0;i<ac.sz;i++){ for(int j=0;j<sigma_size;j++){ int v=ac.ch[i][j]; if(ac.val[v]) continue; else if(ac.last[v]) continue; A.maze[i][v]++; } } for(int i=0;i<=ac.sz;i++) A.maze[i][ac.sz]=1; Matrix B; B.init(2); B.maze[0][0]=26;B.maze[0][1]=1; B.maze[1][0]=0; B.maze[1][1]=1; ull ans=qpow(B,m); ull tmp=qpow(A,m); cout<<ans-tmp<<endl; } return 0; }
我就好好搞通图论了
hdu 3639(起码也是个多校题,虽然也许是多校签到题)
题意:许多人可以互相支持,比如A支持B,但是他不能支持自己,然后问你最多被支持的人被支持了多少次,分别是哪几个人
题解:有向边先建图(这题边要反向建图,比如A支持B,那就要B->A这样建图,原因是等会找多少人支持了B,这要方便搜索)
然后就是tarjan缩点,如今我的tarjan模版已经写的比较熟练比较6了,可以直接套了,每次找强连通分量的时候需要记录每个强连通分量中有多少点
同一个SCC中的点肯定都是互相支持的,如果B所在的SCC有10个点,那么就有9个人支持他
然后就是缩点之后建新图,并且记录每个SCC的入度
然后从入度为0的SCC开始dfs,记得要做标记(开头没做标记WA了,找了好久才发现错误),dfs一直搜到底,记录最大值,最后最大值-1就是支持最多的次数,然后就看每个人所属的SCC的搜到的值等于最大值,然后记录
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 5005 #define MAXN 1000005 #define maxnode 110 #define sigma_size 26 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const LL mod = (1<<64); /**************¶ÁÈëÍâ¹Ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct Edge{ int v,next; }edge[30005]; int head[MAX]; int dfn[MAX]; int low[MAX]; int instack[MAX]; int sstack[MAX]; int belong[MAX]; int num[MAX]; int tmp[MAX]; int in[MAX]; int vis[MAX]; int tot,Index,cnt,top; vector<int> v[MAX]; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void init(){ mem(head,-1); mem(dfn,0); mem(low,0); mem(instack,0); mem(belong,0); mem(num,0); mem(tmp,0); mem(in,0); mem(vis,0); top=0; Index=0; cnt=0; tot=0; } void tarjan(int u){ dfn[u]=low[u]=++Index; sstack[++top]=u; instack[u]=1; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!dfn[v]){ tarjan(v); low[u]=min(low[v],low[u]); } else if(instack[v]) low[u]=min(dfn[v],low[u]); } if(dfn[u]==low[u]){ cnt++; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; num[cnt]++; if(k==u) break; } } } int dfs(int u){ vis[u]=1; int ans=num[u]; for(int i=0;i<v[u].size();i++){ int k=v[u][i]; if(!vis[k]){ ans+=dfs(k); } } return ans; } int main(){ int t,kase=0; scanf("%d",&t); while(t--){ int n,m; scanf("%d%d",&n,&m); init(); for(int i=0;i<m;i++){ int a=read_int(); int b=read_int(); add_edge(b,a); } for(int i=0;i<n;i++){ if(!dfn[i]) tarjan(i); } for(int i=1;i<=cnt;i++) v[i].clear(); for(int i=0;i<n;i++){ for(int j=head[i];j!=-1;j=edge[j].next){ int vv=edge[j].v; if(belong[vv]!=belong[i]){ in[belong[vv]]++; v[belong[i]].push_back(belong[vv]); } } } int maxn=0; for(int i=1;i<=cnt;i++){ if(!in[i]){ mem(vis,0); tmp[i]=dfs(i)-1; maxn=max(maxn,tmp[i]); } } vector<int> vans; for(int i=0;i<n;i++){ if(tmp[belong[i]]==maxn) vans.push_back(i); } kase++; printf("Case %d: %d\n",kase,maxn); for(int i=0;i<vans.size();i++){ if(i==0) printf("%d",vans[i]); else printf(" %d",vans[i]); } printf("\n"); } return 0; }
poj 2762
题意:给你一些有向边,给的应该是连通图,然后问你随便选两个点,能否从A走到B,或者从B走到A(看清楚是或者 or)
题解:这题是弱连通,就是连通图里任意两点,只要有一点能到另一点即可
首先用tarjan缩点去环,因为环中的点必然满足条件,然后考虑这是一棵树,边是有向边,A是根,B,C是他的儿子,那么B必然不能到达C,C也不能到达B,所以这个图如果要是弱连通,那么必须是一个链
那么考虑下入度和出度即可,只有一个入度为0的点,一个出度为0的点,还有都是入度=出度的点‘
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 5005 #define MAXN 1000005 #define maxnode 110 #define sigma_size 26 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const LL mod = (1<<64); /**************¶ÁÈëÍâ¹Ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct Edge{ int v,next; }edge[30005]; int head[MAX]; int dfn[MAX]; int low[MAX]; int instack[MAX]; int sstack[MAX]; int belong[MAX]; int in[MAX]; int out[MAX]; int tot,Index,cnt,top; void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void init(){ mem(head,-1); mem(dfn,0); mem(low,0); mem(instack,0); mem(belong,0); mem(in,0); mem(out,0); top=0; Index=0; cnt=0; tot=0; } void tarjan(int u){ dfn[u]=low[u]=++Index; sstack[++top]=u; instack[u]=1; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!dfn[v]){ tarjan(v); low[u]=min(low[v],low[u]); } else if(instack[v]) low[u]=min(dfn[v],low[u]); } if(dfn[u]==low[u]){ cnt++; while(1){ int k=sstack[top--]; instack[k]=0; belong[k]=cnt; if(k==u) break; } } } int main(){ int t; scanf("%d",&t); while(t--){ int n,m; scanf("%d%d",&n,&m); init(); for(int i=0;i<m;i++){ int a=read_int(); int b=read_int(); add_edge(a,b); } for(int i=1;i<=n;i++){ if(!dfn[i]) tarjan(i); } for(int i=1;i<=n;i++){ for(int j=head[i];j!=-1;j=edge[j].next){ int v=edge[j].v; if(belong[v]!=belong[i]){ in[belong[v]]++; out[belong[i]]++; } } } int ans=0; int flag=0; for(int i=1;i<=cnt;i++){ if(!in[i]) ans++; if(out[i]>1) flag=1; if(ans>1) flag=1; } if(flag) printf("No\n"); else printf("Yes\n"); } return 0; }
hdu 3394
题意:给你一个连通图,没有重边(好啊),然后就是每个旅游路线是一个环,如果不在路线中的边就是可以去掉,如果在多个环中的边就是会冲突
让你求有多少边可以去掉,多少边冲突
题解:可以去掉的边就是无向图的桥,冲突的边就是在双连通分量中
在一个会起冲突的双连通分量中,必然是没有割点的,然而边-双连通分量有可能会有割点,而点-双连通分量就是没有割点的
然而会起冲突,说明这个点-双连通分量中的边比点多,想一下就是了,然后就是怎么求的问题了
套用了点-双连通分量的模版,然而这个并不能求边的数量,所以在stack里面存的是这个点-双连通分量的边,并且tarjan要做一点修改,如果是向已经走过的点更新,那么只要和比自己时间戳dfn小的点比较就行了,因为这是无向图,你在考虑比你时间戳大的点的时候,如果这个点已经走过了,那么在考察这个点的时候,他已经和前面的点更新过了,所以这会就不要考虑了,因为在每次要更新的地方把双连通分量中的边放入stack中
做个点双连通分量的模版把
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 10005 #define MAXN 200005 #define maxnode 1005 #define sigma_size 4 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) #define pii pair<int,int> const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const int mod = 9937; /**************¶áèëía1ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*****************************************************/ struct Edge{ int u,v,next; } edge[MAXN]; int head[MAX]; int dfn[MAX]; int low[MAX]; int vis[MAXN]; int num[MAX];//记录点双连通分量中有多少边 int belong[MAX]; int tot,Index,cnt,bridge,ans; vector<int> ddc[MAX]; stack<Edge> q; void add_edge(int a,int b){ edge[tot]=(Edge){a,b,head[a]}; head[a]=tot++; } void init(){ mem(head,-1); mem(dfn,0); mem(low,0); mem(num,0); mem(vis,0); mem(belong,0); tot=Index=cnt=bridge=ans=0; } void tarjan(int u,int fa){ dfn[u]=low[u]=++Index; for(int i=head[u]; i!=-1; i=edge[i].next){ int v=edge[i].v; if(!dfn[v]){ q.push(edge[i]); tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]){ bridge++; q.pop();//这条边是桥,直接弹出 } else if(dfn[u]<=low[v]){ cnt++; ddc[cnt].clear(); while(1){ Edge k=q.top(); q.pop(); num[cnt]++; if(belong[k.u]!=cnt){ ddc[cnt].push_back(k.u); belong[k.u]=cnt; } if(belong[k.v]!=cnt){ ddc[cnt].push_back(k.v); belong[k.v]=cnt; } if(k.u==u&&k.v==v) break; } } } else if(dfn[u]>dfn[v]&&v!=fa) { q.push(edge[i]);//不能重复 low[u]=min(low[u],dfn[v]); } } } int main(){ int n,m; while(scanf("%d%d",&n,&m)&&n){ init(); for(int i=0; i<m; i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } for(int i=0; i<n; i++){ if(!dfn[i]) tarjan(i,-1); } for(int i=1;i<=cnt;i++){ if(num[i]>ddc[i].size()) ans+=num[i]; } printf("%d %d\n",bridge,ans); } return 0; }
hdu 2242
题意:这么显然,就是把一棵树切成两半,然后两边最小差值
题解:tarjan缩点(如今理解了点和边双连通的不同,这个缩点是只要成环的都在一起就行,不考虑有没有割点,所以是边-双连通分量)
并且记录每个连通分量中有多少点
然后就是dfs搜索啦,当成一棵树,从根节点进去,考虑每个儿子的中的节点数量,就是搜树的重心,唉我不会用树形dp写啊,太搓了
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 10005 #define MAXN 100005 #define maxnode 1005 #define sigma_size 4 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const LL mod = (1<<64); /**************¶áèëía1ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct Edge{ int v,next; }edge[MAXN]; int head[MAX]; int dfn[MAX]; int low[MAX]; int instack[MAX]; int sstack[MAX]; int belong[MAX]; int in[MAX]; int vis[MAX]; int num[MAX]; int tmp[MAX]; int tot,Index,top,cnt,sum,ans; vector<int> v[MAX]; void init(){ mem(head,-1); mem(dfn,0); mem(low,0); mem(instack,0); mem(belong,0); mem(num,0); mem(tmp,0); mem(vis,0); tot=0; Index=0; top=0; cnt=0; sum=0; ans=INF; } void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void tarjan(int u,int fa){ dfn[u]=low[u]=++Index; instack[u]=1; sstack[++top]=u; int flag=0; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(v==fa&&!flag){ flag=1; continue; } if(!dfn[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(dfn[v],low[u]); } if(low[u]==dfn[u]){ cnt++; while(1){ int k=sstack[top--]; belong[k]=cnt; instack[k]=0; tmp[cnt]+=num[k]; if(k==u) break; } } } int dfs(int u,int fa){ int ret=tmp[u]; for(int i=0;i<v[u].size();i++){ int k=v[u][i]; if(k==fa) continue; ret+=dfs(k,u); } ans=min(ans,abs(sum-2*ret)); return ret; } int main(){ int n,m; while(~scanf("%d%d",&n,&m)){ init(); for(int i=0;i<n;i++){ scanf("%d",&num[i]); sum+=num[i]; } for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); add_edge(b,a); } for(int i=0;i<n;i++){ if(!dfn[i]) tarjan(i,-1); } if(cnt==1){ printf("impossible\n"); continue; } for(int i=1;i<=cnt;i++) v[i].clear(); for(int i=0;i<n;i++){ for(int j=head[i];j!=-1;j=edge[j].next){ int vv=edge[j].v; if(belong[i]!=belong[vv]){ v[belong[i]].push_back(belong[vv]); } } } dfs(1,-1); printf("%d\n",ans); } return 0; }
hdu 3861
题意:国王要把国家分成几个州,满足一些条件,如果两个点属于同一个强连通,那么他们必须在同一个州,在同一个州里的点,必须满足弱连通
题解:tarjan缩点,变成一棵树,然后从入度为0的点放去队列中,bfs+dfs,走一条链,走过一个点就标记,并且把其他与他相连的点入度-1,看最后有多少个链(弱连通)
#include <map> #include <set> #include <stack> #include <queue> #include <cmath> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstring> #include <sstream> #include <cstdlib> #include <iostream> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define MAX 5005 #define MAXN 100005 #define maxnode 1005 #define sigma_size 4 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define lrt rt<<1 #define rrt rt<<1|1 #define mid int m=(r+l)>>1 #define LL long long #define ull unsigned long long #define mem(x,v) memset(x,v,sizeof(x)) #define lowbit(x) (x&-x) const int prime = 999983; const int INF = 0x3f3f3f3f; const int INFF = 1e9; const double pi = 3.141592653589793; const double inf = 1e18; const double eps = 1e-10; const LL mod = (1<<64); /**************¶áèëía1ò*********************/ inline int read_int(){ int ret=0; char tmp; while(!isdigit(tmp=getchar())); do{ ret=(ret<<3)+(ret<<1)+tmp-'0'; } while(isdigit(tmp=getchar())); return ret; } /*******************************************/ struct Edge{ int v,next; }edge[MAXN]; int head[MAX]; int dfn[MAX]; int low[MAX]; int instack[MAX]; int sstack[MAX]; int belong[MAX]; int in[MAX]; int vis[MAX]; int num[MAX]; int tot,Index,top,cnt; vector<int> v[MAX]; queue<int> q; void init(){ mem(head,-1); mem(dfn,0); mem(low,0); mem(instack,0); mem(belong,0); mem(num,0); mem(in,0); mem(vis,0); tot=0; Index=0; top=0; cnt=0; } void add_edge(int a,int b){ edge[tot]=(Edge){b,head[a]}; head[a]=tot++; } void tarjan(int u){ dfn[u]=low[u]=++Index; instack[u]=1; sstack[++top]=u; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(dfn[v],low[u]); } if(low[u]==dfn[u]){ cnt++; while(1){ int k=sstack[top--]; belong[k]=cnt; instack[k]=0; num[cnt]++; if(k==u) break; } } } void dfs(int u){ int flag=0; for(int i=0;i<v[u].size();i++){ int k=v[u][i]; if(vis[k]) continue; if(!flag&&!vis[k]){ vis[k]=1; dfs(k); flag=1; } else{ in[k]--; if(in[k]==0){ q.push(k); vis[k]=1; } } } } int main(){ int t; scanf("%d",&t); while(t--){ int n,m; scanf("%d%d",&n,&m); init(); for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); add_edge(a,b); } for(int i=1;i<=n;i++){ if(!dfn[i]) tarjan(i); } for(int i=1;i<=cnt;i++) v[i].clear(); for(int i=1;i<=n;i++){ for(int j=head[i];j!=-1;j=edge[j].next){ int vv=edge[j].v; if(belong[i]!=belong[vv]){ in[belong[vv]]++; v[belong[i]].push_back(belong[vv]); } } } while(!q.empty()) q.pop(); for(int i=1;i<=cnt;i++){ if(!in[i]){ vis[i]=1; q.push(i); } } int ans=0; while(!q.empty()){ int u=q.front(); q.pop(); ans++; dfs(u); } printf("%d\n",ans); } return 0; }
这题其实还有另外种想法,就是二分图最小路径覆盖(路径是一条链啊,然后覆盖所有点,需要多少条路径)
由于给的图不是二分图,所以要把一个点拆成x部和y部,然后建边,在用hk算法匹配的时候只要写y部的i点匹配x部的j点就行,然后就不需要实际把两个点拆开
我二分图理解还不够深刻,这个也是想了好久,然后我的HK算法代码就是x部和y部都match了,比较搓,还没好好学