在学校模拟赛中出现了一些比较有意思的题目,在这里写出来总结一下。一篇博客就写3天,也就是9题吧。
2017.10.23 Problem A
题目大意: 有一列数:111~nnn,每次从其中选出两个求平均值,然后放回数列中,求最后剩下的数的最大值,对大素数取模。
做法: 本题需要用到贪心+数学。
首先可以证明每次选最小的两个是最优的,那么最后得到的值要怎么计算呢?
我们知道要求的式子是:((...(((1+2)×12+3)×12+4)...)×12+n)×12((...(((1+2)\times \frac{1}{2}+3)\times \frac{1}{2}+4)...)\times \frac{1}{2}+n)\times \frac{1}{2}((...(((1+2)×21+3)×21+4)...)×21+n)×21,那么:
原式=12n−1+∑i=2ni2n−i+1=12n−1+∑i=2ni2i−22n−1=2n−1+∑i=0n−2(2n−1−2i)2n−1=n−1+12n−1原式=\frac{1}{2^{n-1}}+\sum_{i=2}^n \frac{i}{2^{n-i+1}}=\frac{1}{2^{n-1}}+\sum_{i=2}^n\frac{i2^{i-2}}{2^{n-1}}=\frac{2^{n-1}+\sum_{i=0}^{n-2}(2^{n-1}-2^i)}{2^{n-1}}=n-1+\frac{1}{2^{n-1}}原式=2n−11+∑i=2n2n−i+1i=2n−11+∑i=2n2n−1i2i−2=2n−12n−1+∑i=0n−2(2n−1−2i)=n−1+2n−11
至于倒数第二个式子是怎么推出来的,可以手画个矩阵找规律,第一列写111个202^020,第二列写222个202^020,第三列写333个212^121,…,第nnn列写nnn个2n−22^{n-2}2n−2,我们发现我们要求的就是这一堆东西的和除以2n−12^{n-1}2n−1,然后我们知道每一行和的和等于每一列和的和,就可以推出上面的式子了。然后用快速幂和逆元处理一下即可。时间复杂度O(logn)O(\log n)O(logn)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000000007
using namespace std;
ll n;
ll power(ll a,ll b)
{
ll s=1,ss=a;
while(b)
{
if (b&1) s=(s*ss)%mod;
b>>=1,ss=(ss*ss)%mod;
}
return s;
}
int main()
{
scanf("%lld",&n);
if (n==0) printf("0");
else printf("%lld",(n-1+power(power(2,n-1),mod-2))%mod);
return 0;
}
2017.10.23 Problem B
题目大意: 给出一个带边权的无向连通图,令dis(i,j)dis(i,j)dis(i,j)为点i,ji,ji,j之间路径上边权最小值的最大值,所有无序点对(i,j)(i,j)(i,j)的dis(i,j)dis(i,j)dis(i,j)中第kkk大的值。
做法: 本题需要用到Kruskal算法求最大生成树。
分析题目,我们发现求个最大生成树后,两点之间路径上边权的最小值就是它们的disdisdis。那么考虑怎么快速求和,我们发现,因为求最大生成树时,边是按边权从大到小的顺序插入的,那么每当一条边连通两个连通块时,对于两点处于不同连通块的点对,它们之间的disdisdis就是当前所添加的边的边权,那么我们用并查集记录集合中的点数,每次合并时,就有两个集合中点数之积个无序点对的disdisdis为当前边权,因为边权按从大到小顺序处理,就这样找第kkk大即可。时间复杂度O(mlogm)O(m\log m)O(mlogm)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,ed[100010],fa[100010],tot;
ll k,siz[100010],num[100010];
struct edge {int u,v,w;} e[200010];
int find(int x)
{
int r=x,i=x,j;
while (r!=fa[r]) r=fa[r];
while (i!=r) j=fa[i],fa[i]=r,i=j;
return r;
}
void merge(int x,int y,int w)
{
int fx=find(x),fy=find(y);
ed[++tot]=w;
num[tot]=siz[fx]*siz[fy];
fa[fx]=fy,siz[fy]+=siz[fx];
}
bool cmp(edge a,edge b)
{
return a.w>b.w;
}
void kruskal()
{
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
tot=0;
for(int i=1;i<=m;i++)
{
if (tot>=n-1) break;
if (find(e[i].u)!=find(e[i].v)) merge(e[i].u,e[i].v,e[i].w);
}
}
int main()
{
scanf("%d%d%lld",&n,&m,&k);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
kruskal();
for(int i=1;i<=tot;i++)
{
if (k<=num[i]) {printf("%d",ed[i]);break;}
else k-=num[i];
}
return 0;
}
2017.10.23 Problem C
题目大意: 给定一个由小写字母组成的字符串,每个位置有一个权值difdifdif,要将字符串分为若干段连续的区间,一段连续段[l,r][l,r][l,r]的代价取下列情况的最小值:
情况1:代价为a×(∑i=lrdif(i))2+ba\times (\sum_{i=l}^rdif(i))^2+ba×(∑i=lrdif(i))2+b。
情况2:若该段内出现次数最多的字符出现的次数在[L,R][L,R][L,R]之间,代价为c×∑i=lrdif(i)+dc\times\sum_{i=l}^rdif(i)+dc×∑i=lrdif(i)+d。
求字符串每一个前缀划分后的最小代价。
做法: 本题需要用到斜率+单调队列优化DP。
其实挺容易看出来情况1就是一个标准斜率优化DP的式子,那么第二种情况是什么呢?观察后得出,当rrr变大时,满足情况2的lll的区间也是单调向右移动的,用单调队列维护这一种情况,然后取上述两种情况的最小值即可。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
int n,l,r,ll[100010],rr[100010],now[30]={0},q[100010],qq[100010],h,t,hh,tt,nowr;
LL a,b,c,d,inf,dif[100010],sum[100010],f[100010];
char s[100010];
struct point
{
LL x,y;
point operator - (point a) const
{
point now;
now.x=x-a.x;
now.y=y-a.y;
return now;
}
} p[100010],check;
int maxx()
{
int mx=0;
for(int i=0;i<26;i++) mx=max(mx,now[i]);
return mx;
}
LL multi(point a,point b)
{
return a.x*b.y-b.x*a.y;
}
int main()
{
inf=1000000000;
inf*=inf;
scanf("%d%lld%lld%lld%lld%d%d",&n,&a,&b,&c,&d,&l,&r);
scanf("%s",s+1);
sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&dif[i]);
sum[i]=sum[i-1]+dif[i];
}
int x=1;
for(int i=1;i<=n;i++)
{
now[s[i]-'a']++;
while (maxx()>r) now[s[x]-'a']--,x++;
ll[i]=x;
}
memset(now,0,sizeof(now));
x=n+1;
for(int i=n;i>=1;i--)
{
if (i<n) now[s[i+1]-'a']--;
while (maxx()<l&&x>1) now[s[--x]-'a']++;
if (maxx()<l) rr[i]=0;
else rr[i]=x;
}
f[0]=0;
h=1,q[t=1]=0;
nowr=0;
p[0].x=p[0].y=0;
hh=1,qq[tt=1]=0;
for(int i=1;i<=n;i++)
{
f[i]=inf;
while(h<=t&&q[h]<ll[i]-1) h++;
for(;nowr<rr[i]-1;)
{
if (nowr+1>=ll[i]-1)
{
++nowr;
while(h<=t&&f[q[t]]-c*sum[q[t]]>=f[nowr]-c*sum[nowr]) t--;
q[++t]=nowr;
}
else if (nowr<rr[i]-1) ++nowr;
}
if (ll[i]<=rr[i]&&h<=t) f[i]=min(f[i],f[q[h]]+c*(sum[i]-sum[q[h]])+d);
check.x=1,check.y=2*a*sum[i];
while(hh<tt&&multi(p[qq[hh+1]]-p[qq[hh]],check)>=0) hh++;
f[i]=min(f[i],f[qq[hh]]+a*(sum[i]-sum[qq[hh]])*(sum[i]-sum[qq[hh]])+b);
p[i].x=sum[i],p[i].y=a*sum[i]*sum[i]+f[i];
while(hh<tt&&multi(p[qq[tt]]-p[qq[tt-1]],p[i]-p[qq[tt]])<=0) tt--;
qq[++tt]=i;
printf("%lld\n",f[i]);
}
return 0;
}
2017.10.24 Problem A
题目大意: 给出mmm个区间[Li,Ri][L_i,R_i][Li,Ri],求使得用前kkk个区间和kkk个长度为xxx的区间能覆盖[1,n][1,n][1,n]的最小的kkk。
做法: 本题需要用到二分答案+贪心。
首先很容易看出答案具有单调性,然后对于每一个二分到的midmidmid,将前midmidmid个区间按左端点为第一关键字,右端点为第二关键字排序,然后从前往后贪心,如果当前区间左端点比当前覆盖到的位置要大,那么用一个长度为xxx的区间去补,更新当前覆盖到的位置。做完之后看区间个数超不超过midmidmid即可。注意特判x=0x=0x=0的情况。时间复杂度为O(mlog2m)O(m\log^2 m)O(mlog2m)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,x,l[100010],r[100010];
int m;
struct interval {ll l,r;} s[100010];
bool cmp(interval a,interval b)
{
if (a.l!=b.l) return a.l<b.l;
else return a.r<b.r;
}
bool check(int mid)
{
s[0].l=s[0].r=0;
s[mid+1].l=s[mid+1].r=n+1;
for(int i=1;i<=mid;i++)
s[i].l=l[i],s[i].r=r[i];
sort(s+1,s+mid+1,cmp);
ll now=0,times=0,add;
for(int i=1;i<=mid+1;i++)
{
if (s[i].l>now+1)
{
if (x==0) return 0;
add=((s[i].l-now-1)%x==0)?((s[i].l-now-1)/x):((s[i].l-now-1)/x+1);
times+=add;
now+=add*x;
}
now=max(now,s[i].r);
}
if (times>mid) return 0;
else return 1;
}
int main()
{
scanf("%lld%d%lld",&n,&m,&x);
for(int i=1;i<=m;i++)
scanf("%lld%lld",&l[i],&r[i]);
int L=0,R=m+1;
l[m+1]=r[m+1]=0;
while(L<R)
{
int mid=(L+R)>>1;
if (check(mid)) R=mid;
else L=mid+1;
}
if (L==m+1) printf("Poor Douer!");
else printf("%d",L);
return 0;
}
2017.10.24 Problem B
题目大意: 要选出n(≤109)n(\le 10^9)n(≤109)个数,这些数在111~m(≤109)m(\le 10^9)m(≤109)之间,要求后面的数要比前面的数大,而且某些位置上某些数不能取(这些条件有p(≤2000)p(\le 2000)p(≤2000)个),问有多少种合法的取法。
做法: 本题需要用到Lucas定理。
令f(i,j)f(i,j)f(i,j)为第iii个数选jjj的方案数,易得状态转移方程:
f(i,j)=∑k=1j−1f(i−1,j)f(i,j)=\sum_{k=1}^{j-1} f(i-1,j)f(i,j)=∑k=1j−1f(i−1,j)
边界条件为f(0,0)=1f(0,0)=1f(0,0)=1,以上式子成立当且仅当第iii个数能选jjj,否则f(i,j)=0f(i,j)=0f(i,j)=0。最后的答案显然为∑i=1mf(n,i)=f(n+1,m+1)\sum_{i=1}^{m}f(n,i)=f(n+1,m+1)∑i=1mf(n,i)=f(n+1,m+1)。
可是上面这个式子是O(nm)O(nm)O(nm)的,显然炸到飞起,考虑优化。
注意到上述递推式可转化为f(i,j)=f(i,j−1)+f(i−1,j−1)f(i,j)=f(i,j-1)+f(i-1,j-1)f(i,j)=f(i,j−1)+f(i−1,j−1),觉不觉得有点像组合数的递推式?实际上,若没有数是不可取的,那么f(n,m)=Cmnf(n,m)=C_m^nf(n,m)=Cmn。问题是现在有了一些数是不可选的,那么我们考虑将一个f(i,j)f(i,j)f(i,j)变成000对另一个f(x,y)f(x,y)f(x,y)的影响。经过分析,当f(i,j)f(i,j)f(i,j)变成000,那么f(x,y)f(x,y)f(x,y)将变成f(x,y)−Cy−j−1x−i−1×f(i,j)f(x,y)-C_{y-j-1}^{x-i-1}\times f(i,j)f(x,y)−Cy−j−1x−i−1×f(i,j)。所以我们将不可取的点从上到下,从左到右排序,然后一个一个变成000,并计算它对答案以及对其他不可取点的贡献即可,时间复杂度为O(p2logn)O(p^2\log n)O(p2logn)。
然而这样会TLE,我们可以利用Lucas定理+预处理优化时间复杂度到O(p2)O(p^2)O(p2),详细的优化方法请看代码。
(有大佬说这题有点像APIO2016的赛艇,没做过,下次看看)
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000003
using namespace std;
ll n,m,fac[1000010],inv[1000010],invf[1000010];
ll ans,now[2010];
struct point {ll a,b;} pp[2010];
int p;
ll calc_c(ll n,ll m)
{
if (m>n) return 0;
return fac[n]%mod*invf[m]%mod*invf[n-m]%mod;
}
ll lucas(ll n,ll m)
{
if (n<0||m<0||m>n) return 0;
if (m==0) return 1;
return calc_c(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}
bool cmp(point a,point b)
{
if (a.a!=b.a) return a.a<b.a;
else return a.b<b.b;
}
int main()
{
scanf("%lld%lld%d",&n,&m,&p);
fac[0]=inv[0]=invf[0]=1;
fac[1]=inv[1]=invf[1]=1;
for(ll i=2;i<=1000003;i++)
{
fac[i]=(fac[i-1]*i)%mod;
inv[i]=(mod-(mod/i))%mod*inv[mod%i]%mod;
invf[i]=(invf[i-1]*inv[i])%mod;
}
ans=lucas(m,n);
for(int i=1;i<=p;i++)
scanf("%lld%lld",&pp[i].a,&pp[i].b);
sort(pp+1,pp+p+1,cmp);
for(int i=1;i<=p;i++)
now[i]=lucas(pp[i].b-1,pp[i].a-1);
for(int i=1;i<=p;i++)
{
ans=(((ans-now[i]*lucas(m-pp[i].b,n-pp[i].a))%mod)+mod)%mod;
for(int j=i+1;j<=p;j++) now[j]=(((now[j]-now[i]*lucas(pp[j].b-pp[i].b-1,pp[j].a-pp[i].a-1))%mod)+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
2017.10.24 Problem C
题目大意: 一条有nnn个点的链,走过一条链边代价为1,有mmm个传送门,传送门是双向的,走过没有代价,但是只能走一次,现在可以额外建造ppp个传送门,求以任意一点为起点,任意一点为终点,走过所有链边和所有传送门所需要的最小代价。
做法: 本题需要用到贪心+堆+链表。
分析后发现题目中最后的路线是一条欧拉路,而一个无向图存在欧拉路的条件是只有一对度数为奇数的点,那么我们就需要先用ppp个传送门删去ppp对奇点,然后选定一对奇点作为起终点(可以看做删去),这些都是没有代价产生的,而接下来我们要连接剩下的奇点对,而这时连接一对奇点的代价为它们在链上中间的边数,这时肯定是连接从前往后第一个奇点和第二个奇点,第三个奇点和第四个奇点…这样连接是最优的。因此我们可以将问题转化为,对于一些代价a1,a2,...,ata_1,a_2,...,a_ta1,a2,...,at,从中选出若干个不相邻的元素使得最后代价最小,那么此时O(m2)O(m^2)O(m2)的DP就很容易想出来了,不过这不是问题最后的解。
此时出题人用了一种模拟费用流退流的方法来进行贪心,具体怎么证我还不清楚,但是步骤如下:
1.找到最小的代价,累加入答案。
2.将其代价改为在链表上左右两点代价之和减去原来的代价。
3.删去其在链表上的左右两点。
于是用一个堆和一个链表维护这个贪心,就解决了这一问题,时间复杂度O(mlogm)O(m\log m)O(mlogm)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
ll inf,n,f[300010],val[300010],ans;
int m,p,pre[300010],next[300010],t=0;
bool vis[300010]={0};
struct state
{
int id;
ll val;
bool operator < (state a) const
{
return val>a.val;
}
};
priority_queue<state> Q;
bool cmp(ll a,ll b)
{
return a<b;
}
void del(int x)
{
next[pre[x]]=next[x];
pre[next[x]]=pre[x];
vis[x]=1;
}
int main()
{
inf=1000000;
inf*=inf;
scanf("%lld%d%d",&n,&m,&p);
f[1]=1,f[2]=n;
for(int i=1;i<=m;i++)
{
ll a,b;
scanf("%lld%lld",&a,&b);
f[2*i+1]=a,f[2*i+2]=b;
}
sort(f+1,f+2*m+3,cmp);
f[0]=0;
bool last=0;
int l=0;
for(int i=1;i<=2*m+2;i++)
{
if (f[i]!=f[i-1])
{
if (last)
{
++t;
pre[t]=t-1;
next[t-1]=t;
if (t>1) val[t]=f[i-1]-f[l];
else val[t]=0;
l=i-1;
}
last=0;
}
last=!last;
}
if (last)
{
++t;
pre[t]=t-1;
next[t-1]=t;
if (t>1) val[t]=f[2*m+2]-f[l];
else val[t]=0;
}
next[t]=t+1;
pre[t+1]=t;
val[1]=val[t+1]=inf;
for(int i=2;i<=t;i++)
{
state now;
now.id=i;
now.val=val[i];
Q.push(now);
}
ans=0;
p=t/2-1-p;
while(p>0)
{
p--;
while(vis[Q.top().id]) Q.pop();
state now=Q.top();Q.pop();
ans+=now.val;
val[now.id]=val[pre[now.id]]+val[next[now.id]]-val[now.id];
del(pre[now.id]);
del(next[now.id]);
now.val=val[now.id];
Q.push(now);
}
printf("%lld",n-1+ans);
return 0;
}
2017.10.25 Problem A
题目大意: 给定一个长为n(≤300000)n(\le 300000)n(≤300000)的字符串AAA和一个长为m(≤200)m(\le 200)m(≤200)的字符串BBB,问有多少个区间[l,r][l,r][l,r]满足AAA在这个区间内包含和BBB相同的子串。
做法: 本题需要用到DP计数。
令f(i,j)f(i,j)f(i,j)为最大的kkk使得A[k,i]A[k,i]A[k,i]内包含B[1,j]B[1,j]B[1,j]这个子串,易得状态转移方程:
f(i,j)=max(f(i−1,j−1),f(i−1,j))f(i,j)=max(f(i-1,j-1),f(i-1,j))f(i,j)=max(f(i−1,j−1),f(i−1,j)) (A[i]=B[j])(A[i]=B[j])(A[i]=B[j])
f(i,j)=f(i−1,j)f(i,j)=f(i-1,j)f(i,j)=f(i−1,j) (A[i]≠B[j])(A[i]\ne B[j])(A[i]=B[j])
j=1j=1j=1的时候特判一下即可。这个状态转移方程是O(nm)O(nm)O(nm)的,可以接受。可是算出来这个东西有什么用呢?注意到f(i,m)f(i,m)f(i,m)的意义,可以知道区间[1,i],[2,i],...,[f(i,m),i][1,i],[2,i],...,[f(i,m),i][1,i],[2,i],...,[f(i,m),i]都满足题目条件,因此我们累加所有的f(i,m)f(i,m)f(i,m)就是答案。求的时候可以用滚动数组将空间优化到O(m)O(m)O(m)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,m,f[2][210],ans,now,past;
char a[300010],b[210];
int main()
{
scanf("%s",a);
scanf("%s",b);
n=strlen(a),m=strlen(b);
ans=0,now=0,past=1;
memset(f,0,sizeof(f));
for(ll i=0;i<n;i++)
{
for(ll j=0;j<m;j++)
{
if (a[i]==b[j])
{
if (j==0) f[now][j]=i+1;
else f[now][j]=f[past][j-1];
}
else f[now][j]=f[past][j];
}
ans+=f[now][m-1];
swap(now,past);
}
printf("%lld",ans);
return 0;
}
2017.10.25 Problem B
题目大意: 对一个111~nnn的排列,用冒泡排序生成一个有nnn个点的无向图,步骤是:
枚举iii从111到nnn
枚举jjj从i+1i+1i+1到nnn
如果a[i]>a[j]a[i]>a[j]a[i]>a[j]:添加无向边(a[i],a[j])(a[i],a[j])(a[i],a[j]),交换a[i],a[j]a[i],a[j]a[i],a[j]。
求生成出的无向图的最大独立集大小,而且求出有哪些点必然在最大独立集中。
做法: 本题需要用到DP求解最长上升/下降子序列。
注意到上述算法是在给所有逆序对之间连边,即所有满足i<ji<ji<j并且a[i]>a[j]a[i]>a[j]a[i]>a[j]的点对(i,j)(i,j)(i,j)之间都连有边,而最大独立集是需要满足集合中两两间都没有边,即所有点对(i,j)(i,j)(i,j)都满足a[i]<a[j]a[i]<a[j]a[i]<a[j],注意到一个独立集就对应一个上升子序列,那么求最大独立集显然就是求最长上升子序列了,用O(nlogn)O(n\log n)O(nlogn)的DP做法即可。
然而怎么判断一个点一不一定在最长上升子序列中呢?我们令f(i)f(i)f(i)为以a[i]a[i]a[i]结尾的最长上升子序列长度,g(i)g(i)g(i)为以a[i]a[i]a[i]开头的最长上升子序列长度(其实就是反着求最长下降子序列),已求出的最长上升子序列长度为ansansans,那么若f(i)+g(i)−1=ansf(i)+g(i)-1=ansf(i)+g(i)−1=ans,则a[i]a[i]a[i]可能在最长上升子序列中,而且它必然在最长上升子序列的第f(i)f(i)f(i)个位置,因此我们只要记录最长上升子序列的每个位置是不是唯一的,如果是唯一的,那么那个点就一定在最长上升子序列中。时间复杂度O(nlogn)O(n\log n)O(nlogn)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,a[100010],f[100010],g[100010],d[100010],top,num[100010]={0};
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
top=d[0]=0;
for(int i=1;i<=n;i++)
{
int l=0,r=top;
if (d[top]<=a[i]) f[i]=++top,d[top]=a[i];
else
{
while(l<r)
{
int mid=(l+r)>>1;
if (d[mid]<=a[i]) l=mid+1;
else r=mid;
}
f[i]=l,d[l]=a[i];
}
}
printf("%d\n",top);
memset(d,0,sizeof(d));
top=0,d[0]=n+1;
for(int i=n;i>=1;i--)
{
int l=0,r=top;
if (d[top]>=a[i]) g[i]=++top,d[top]=a[i];
else
{
while(l<r)
{
int mid=(l+r)>>1;
if (d[mid]>=a[i]) l=mid+1;
else r=mid;
}
g[i]=l,d[l]=a[i];
}
}
for(int i=1;i<=n;i++)
if (f[i]+g[i]-1==top) num[f[i]]++;
for(int i=1;i<=n;i++)
if (f[i]+g[i]-1==top&&num[f[i]]==1) printf("%d ",i);
return 0;
}
2017.10.25 Problem C
题目大意: 有一个带正边权的有向图,两个人分别从点s1,s2s_1,s_2s1,s2出发,分别沿最短路走到t1,t2t_1,t_2t1,t2,问他们最多能走过多少公共点。
做法: 本题需要用到最短路DAG+DAG最长路。
首先我们可以求出从点s1,s2s_1,s_2s1,s2出发的单源最短路,可能在最短路上的边形成一个DAG(有向无环图),我们把它叫做最短路DAG,可以证明两个最短路DAG的交(这里指边集的交,下同)一定也是一个DAG,这个用反证法应该可以证。因此,我们只需要求这个DAG上能经过的最多的点数,也就是DAG最长路即可。然而我们需要注意两个DAG没有交的情况,这时候我们就找可能同时在两条最短路上的点,如果有这样的点答案就是111,否则答案为000。数据卡SPFA,用Dijkstra算法即可,时间复杂度为O(nlogn)O(n\log n)O(nlogn)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int n,m,b=0,s1,t1,s2,t2,tot=0,first[50010][2]={0},firstq[50010]={0},in[50010]={0},f[50010]={0},le=0,mx=0;
ll inf,dis[50010],dist[50010][5];
bool vis[50010];
queue<int> qq;
struct point
{
int v;
ll val;
bool operator < (point a) const
{
return val>a.val;
}
};
priority_queue<point> Q;
struct edge {int v,next;ll d;} e[200010][2],q[200010];
void insert(int a,int b,ll d)
{
e[++tot][0].v=b;
e[tot][0].next=first[a][0];
e[tot][0].d=d;
first[a][0]=tot;
e[tot][1].v=a;
e[tot][1].next=first[b][1];
e[tot][1].d=d;
first[b][1]=tot;
}
void insertq(int a,int b)
{
q[++tot].v=b;
q[tot].next=firstq[a];
firstq[a]=tot;
in[b]++;
}
void dijkstra(int s,int mode,int to)
{
point now,next;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
if (i!=s) dis[i]=inf;
else dis[i]=0;
}
for(int i=first[s][mode];i;i=e[i][mode].next)
dis[e[i][mode].v]=min(dis[e[i][mode].v],e[i][mode].d);
for(int i=1;i<=n;i++)
if (i!=s)
{
now.v=i,now.val=dis[i];
Q.push(now);
}
for(int i=1;i<n;i++)
{
now=Q.top(),Q.pop();
while(vis[now.v]) now=Q.top(),Q.pop();
vis[now.v]=1;
for(int j=first[now.v][mode];j;j=e[j][mode].next)
if (!vis[e[j][mode].v]&&dis[e[j][mode].v]>dis[now.v]+e[j][mode].d)
{
next.v=e[j][mode].v;
next.val=dis[next.v]=dis[now.v]+e[j][mode].d;
Q.push(next);
}
}
for(int i=1;i<=n;i++) dist[i][to]=dis[i];
}
int main()
{
inf=1000000000;
inf*=inf;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;ll d;
scanf("%d%d%lld",&u,&v,&d);
insert(u,v,d);
}
scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
dijkstra(s1,0,1);
dijkstra(s2,0,2);
dijkstra(t1,1,3);
dijkstra(t2,1,4);
if (dist[t1][1]==inf||dist[t2][2]==inf) {printf("-1");return 0;}
tot=0;
for(int i=1;i<=n;i++)
for(int j=first[i][0];j;j=e[j][0].next)
if (dist[i][1]+dist[e[j][0].v][3]+e[j][0].d==dist[t1][1]
&&dist[i][2]+dist[e[j][0].v][4]+e[j][0].d==dist[t2][2]) insertq(i,e[j][0].v),le++;
for(int i=1;i<=n;i++)
if (dist[i][1]+dist[i][3]==dist[t1][1]&&
dist[i][2]+dist[i][4]==dist[t2][2]) {b=1;break;}
for(int i=1;i<=n;i++)
if (!in[i]) qq.push(i);
while(!qq.empty())
{
int v=qq.front();qq.pop();
for(int i=firstq[v];i;i=q[i].next)
{
in[q[i].v]--;
f[q[i].v]=max(f[q[i].v],f[v]+1);
if (!in[q[i].v]) qq.push(q[i].v);
}
}
for(int i=1;i<=n;i++) mx=max(mx,f[i]);
printf("%d",le?mx+1:b);
return 0;
}