期望得分:100+100+100=300
实际得分:70+40+20=130
T1 [SCOI2007]kshort弱化版
Description
有n个城市和m条单向道路,城市编号为1~n。每条道路连接两个不同的城市,且任意两条道路要么起点不同要么终点不同,因此n和m满足m<=n(n-1)。给定两个城市a和b,可以给a到b的所有简单路(所有城市最多经过一次,包括起点和终点)排序:先按长度从小到大排序,长度相同时按照字典序从小到大排序。你的任务是求出a到b的第k短路。
Input
输入第一行包含五个正整数n, m, k, a, b。以下m行每行三个整数u, v, l,表示从城市u到城市v有一条长度为l的单向道路。100%的数据满足:2<=n<=50, 1<=k<=200
Output
如果a到b的简单路不足k条,输出No,否则输出第k短路:从城市a开始依次输出每个到达的城市,直到城市b,中间用减号"-"分割。
基本思路:A*跑k短路
错解:重载运算符时比较字典序,直接输出第k短
错因:
正解:记录所有长度<=第k短长度的路径,取第k条
记录路径的时候,定义vector<结构体>,直接sort
vector是从0开始的!!!!
#include<queue> #include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm> #define N 51 using namespace std; int n,m,k,a,b,sum; short front[N],nxt[N*N],to[N*N],tot; short front2[N],nxt2[N*N],to2[N*N],tot2; int val[N*N],val2[N*N],dis[N]; queue<int>q; struct node { int num,dis2; long long go; short cnt,road[N]; bool operator < (node p) const { return dis2+dis[num]>p.dis2+dis[p.num]; } }cur,nt; vector<node>ans; priority_queue<node>q2; bool vis[N]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-‘0‘; c=getchar(); } } void add(int u,int v,int w) { to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w; to2[++tot2]=u; nxt2[tot2]=front2[v]; front2[v]=tot2; val2[tot2]=w; } void spfa() { memset(dis,63,sizeof(dis)); dis[b]=0; vis[b]=true; q.push(b); int now; while(!q.empty()) { now=q.front(); q.pop(); vis[now]=false; for(int i=front[now];i;i=nxt[i]) if(dis[to[i]]>dis[now]+val[i]) { dis[to[i]]=dis[now]+val[i]; if(!vis[to[i]]) { vis[to[i]]=true; q.push(to[i]); } } } } bool have(long long x,int y) { if(x&(1LL<<y)) return true; return false; } bool cmp(node h,node g) { if(h.dis2!=g.dis2) return h.dis2<g.dis2; for(int i=1;i<=min(h.cnt,g.cnt);i++) if(h.road[i]!=g.road[i]) return h.road[i]<g.road[i]; return h.cnt<g.cnt; } void solve() { if(dis[a]>2e9) { printf("No"); return; } if(a==b) k++; cur.cnt=1; cur.dis2=0; cur.go=1LL<<a; cur.num=cur.road[1]=a; q2.push(cur); while(!q2.empty()) { cur=q2.top(); q2.pop(); if(cur.num==b) { sum++; ans.push_back(cur); if(sum>k && cur.dis2>ans[k-1].dis2) break; } for(int i=front2[cur.num];i;i=nxt2[i]) if(!have(cur.go,to2[i])) { nt.cnt=cur.cnt+1; nt.dis2=cur.dis2+val2[i]; nt.go=cur.go|(1LL<<to2[i]); nt.num=to2[i]; for(int j=1;j<=cur.cnt;j++) nt.road[j]=cur.road[j]; nt.road[nt.cnt]=to2[i]; q2.push(nt); } } if(ans.size()>=k) { sort(ans.begin(),ans.end(),cmp); printf("%d",a); for(int i=2;i<=ans[k-1].cnt;i++) printf("-%d",ans[k-1].road[i]); return; } printf("No"); } int main() { read(n); read(m); read(k); read(a); read(b); int u,v,w; while(m--) { read(u); read(v); read(w); add(v,u,w); } spfa(); solve(); }
T2[ZJOI2007]最大半连通子图
Description
一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意
两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。若G‘=(V‘,E‘)满足V‘?V,E‘是E中所有跟V‘有关的边,
则称G‘是G的一个导出子图。若G‘是G的导出子图,且G‘半连通,则称G‘为G的半连通子图。若G‘是G所有半连通子图
中包含节点数最多的,则称G‘是G的最大半连通子图。给定一个有向图G,请求出G的最大半连通子图拥有的节点数K
,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出C对X的余数。
Input
第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整
数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤1
00000, M ≤1000000;对于100%的数据, X ≤10^8
Output
应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.
tarjan缩环,然后拓扑排序求最长链
注意缩环之后重新构图会出现重边,在拓扑排序里计算方案数时会多算
所以拓扑排序时判断一下是否被同一节点重复更新
错因:没有想到重边,存边的数组大小与点混淆
#include<cstdio> #include<iostream> #include<algorithm> #define N 100001 #define M 1000001 using namespace std; int mod,tot,cnt; int front[N],nxt[M],to[M],from[M]; int front2[N],nxt2[M],to2[M],ru[N]; int dp[N][2],maxn,ans; int dfn[N],low[N],col[N],siz[N],st[N],top; bool vis[N]; int last[N]; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-‘0‘; c=getchar(); } } void add(int u,int v) { to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; from[tot]=u; } void add2(int u,int v) { to2[++tot]=v; nxt2[tot]=front2[u]; front2[u]=tot; ru[v]++; } void tarjan(int now) { low[now]=dfn[now]=++tot; st[++top]=now; vis[now]=true; for(int i=front[now];i;i=nxt[i]) { if(!dfn[to[i]]) { tarjan(to[i]); low[now]=min(low[now],low[to[i]]); } else if(vis[to[i]]) low[now]=min(low[now],dfn[to[i]]); } if(low[now]==dfn[now]) { cnt++; while(top && st[top]!=now) { col[st[top]]=cnt; vis[st[top]]=false; top--; } col[now]=cnt; vis[now]=false; top--; } } void topsort() { top=0; for(int i=1;i<=cnt;i++) if(!ru[i]) { st[++top]=i; dp[i][0]=siz[i]; dp[i][1]=1; } int now; while(top) { now=st[top]; top--; for(int i=front2[now];i;i=nxt2[i]) { if(dp[now][0]+siz[to2[i]]>dp[to2[i]][0]) { dp[to2[i]][0]=dp[now][0]+siz[to2[i]]; dp[to2[i]][1]=dp[now][1]; last[to2[i]]=now; } else if(dp[now][0]+siz[to2[i]]==dp[to2[i]][0]) { if(last[to2[i]]==now) { ru[to2[i]]--; if(!ru[to2[i]]) st[++top]=to2[i]; continue; } last[to2[i]]=now; dp[to2[i]][1]+=dp[now][1]; dp[to2[i]][1]%=mod; } ru[to2[i]]--; if(!ru[to2[i]]) st[++top]=to2[i]; } } } int main() { int n,m; read(n); read(m); read(mod); int u,v; for(int i=1;i<=m;i++) { read(u); read(v); add(u,v); } tot=0; for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;i++) siz[col[i]]++; tot=0; for(int i=1;i<=m;i++) if(col[from[i]]!=col[to[i]]) add2(col[from[i]],col[to[i]]); topsort(); for(int i=1;i<=cnt;i++) maxn=max(maxn,dp[i][0]); for(int i=1;i<=cnt;i++) if(maxn==dp[i][0]) ans+=dp[i][1],ans%=mod; printf("%d\n%d",maxn,ans); } ?
T3[AHOI2006]上学路线route
Description
可可和卡卡家住合肥市的东郊,每天上学他们都要转车多次才能到达市区西端的学校。直到有一天他们两人参加了学校的信息学奥林匹克竞赛小组才发现每天上学的乘车路线不一定是最优的。 可可:“很可能我们在上学的路途上浪费了大量的时间,让我们写一个程序来计算上学需要的最少时间吧!” 合肥市一共设有N个公交车站,不妨将它们编号为1…N的自然数,并认为可可和卡卡家住在1号汽车站附近,而他们学校在N号汽车站。市内有M条直达汽车路线,执行第i条路线的公交车往返于站点pi和qi之间,从起点到终点需要花费的时间为ti。(1<=i<=M, 1<=pi, qi<=N) 两个人坐在电脑前,根据上面的信息很快就编程算出了最优的乘车方案。然而可可忽然有了一个鬼点子,他想趁卡卡不备,在卡卡的输入数据中删去一些路线,从而让卡卡的程序得出的答案大于实际的最短时间。而对于每一条路线i事实上都有一个代价ci:删去路线的ci越大卡卡就越容易发现这个玩笑,可可想知道什么样的删除方案可以达到他的目的而让被删除的公交车路线ci之和最小。 [任务] 编写一个程序: ? 从输入文件中读取合肥市公交路线的信息; ? 计算出实际上可可和卡卡上学需要花费的最少时间; ? 帮助可可设计一个方案,删除输入信息中的一些公交路线,使得删除后从家到学校需要的最少时间变大,而被删除路线的ci和最小;向输出文件输出答案。
Input
输入文件中第一行有两个正整数N和M,分别表示合肥市公交车站和公交汽车路线的个数。以下M行,每行(第i行,总第(i+1)行)用四个正整数描述第i条路线:pi, qi, ti, ci;具体含义见上文描述。
Output
输出文件最多有两行。 第一行中仅有一个整数,表示从可可和卡卡家到学校需要的最短时间。 第二行输出一个整数C,表示Ci之和
思路:找出所有的最短路,重新建图,跑最小割
错因:
确定这条边在最短路上:
正确方法:dis[s,u]+w+dis[v,t]=dis[s,t]
错误方法:从t开始枚举,dis[1,u]+w=dis[1,v],只是计算了1到某个点的最短路
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<queue> #define N 501 #define M 200001 using namespace std; int n,m; int dis[N][N]; int e[M][4]; int src,decc; int tot=1,front2[N],to2[M<<1],nxt2[M<<1],cap[M<<1],lev[N],cur[N]; bool vis[N]; queue<int>q; void read(int &x) { x=0; char c=getchar(); while(!isdigit(c)) c=getchar(); while(isdigit(c)) { x=x*10+c-‘0‘; c=getchar(); } } void add2(int u,int v,int w) { to2[++tot]=v; nxt2[tot]=front2[u]; front2[u]=tot; cap[tot]=w; to2[++tot]=u; nxt2[tot]=front2[v]; front2[v]=tot; cap[tot]=0; } void build() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(k!=i && i!=j && k!=j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); int minn=dis[1][n]; printf("%d\n",minn); for(int i=1;i<=m;i++) { if(dis[1][e[i][0]]+e[i][2]+dis[e[i][1]][n]==minn) add2(e[i][0],e[i][1],e[i][3]); if(dis[1][e[i][1]]+e[i][2]+dis[e[i][0]][n]==minn) add2(e[i][1],e[i][0],e[i][3]); } } bool spfa2() { for(int i=1;i<=n;i++) cur[i]=front2[i],lev[i]=-1; while(!q.empty()) q.pop(); lev[1]=0; q.push(1); int now; while(!q.empty()) { now=q.front(); q.pop(); for(int i=front2[now];i;i=nxt2[i]) if(lev[to2[i]]==-1 && cap[i]>0) { lev[to2[i]]=lev[now]+1; if(to2[i]==n) return true; q.push(to2[i]); } } return false; } int dinic(int now,int flow) { if(now==n) return flow; int delta,rest=0; for(int & i=cur[now];i;i=nxt2[i]) if(lev[to2[i]]>lev[now] && cap[i]>0) { delta=dinic(to2[i],min(cap[i],flow-rest)); if(delta) { cap[i]-=delta; cap[i^1]+=delta; rest+=delta; if(rest==flow) break; } } if(rest!=flow) lev[now]=-1; return rest; } int main() { read(n); read(m); int u,v,t,c; memset(dis,63,sizeof(dis)); for(int i=1;i<=n;i++) dis[i][i]=0; for(int i=1;i<=m;i++) { read(u); read(v); read(t); read(c); e[i][0]=u; e[i][1]=v; e[i][2]=t; e[i][3]=c; dis[u][v]=dis[v][u]=min(dis[u][v],t); } build(); int ans=0; while(spfa2()) ans+=dinic(1,2e9); printf("%d",ans); }