第一题:传递情报 transport
题目描述:一个n个点,m条边的图中有一条边毁坏,只有走到该边起点时才能被发现,求使用一种策略使从1到2最坏情况下走过的距离尽可能短。
题解:考虑用f[i]表示没有路被毁坏地从1走到i,然后发现一条从i出发的路被毁坏,最坏情况下从i到2还要走多远。dp[i]表示最后答案。dp[i]=max(f[i],min(dp[j]+map[i][j])),这个方程式不满足拓扑序,在spfa的过程中转移。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=200+10;
const int M=2000+10;
const int inf=0x3f3f3f3f;
int n,m,now,cnt,fir[N],dp[N];
int dis1[N],dis2[N],f[N];
bool vis[N];
queue<int> q;
struct node{
int s,e,w;
}edg[M];
struct bian{
int v,w,nxt;
}arr[M];
void spfa(int st,int *dis){
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
dis[i]=inf;
while(!q.empty()) q.pop();
q.push(st),dis[st]=0;
while(!q.empty()){
vis[now=q.front()]=0,q.pop();
for(int i=fir[now];i;i=arr[i].nxt)
if(dis[arr[i].v]>dis[now]+arr[i].w){
dis[arr[i].v]=dis[now]+arr[i].w;
if(!vis[arr[i].v])
vis[arr[i].v]=1,q.push(arr[i].v);
}
}
return ;
}
void link(int a,int b,int w){
arr[++cnt].v=b,arr[cnt].w=w;
arr[cnt].nxt=fir[a],fir[a]=cnt;
}
void build(int ban){
memset(fir,0,sizeof fir),cnt=0;
for(int i=1;i<=m;i++)if(i!=ban)
link(edg[i].e,edg[i].s,edg[i].w);
}
int slove(int st,int ed){
memset(vis,0,sizeof vis);
memset(dp,0x3f,sizeof dp);
q.push(st),dp[st]=f[st];
while(!q.empty()){
vis[now=q.front()]=0,q.pop();
for(int i=fir[now];i;i=arr[i].nxt)
if(dp[arr[i].v]>max(dp[now]+arr[i].w,f[arr[i].v])){
dp[arr[i].v]=max(dp[now]+arr[i].w,f[arr[i].v]);
if(!vis[arr[i].v])
vis[arr[i].v]=1,q.push(arr[i].v);
}
}
if(dp[ed]>=inf) return -1;
else return dp[ed];
}
int main(){
freopen("transport.in","r",stdin);
freopen("transport.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&edg[i].s,&edg[i].e,&edg[i].w);
link(edg[i].s,edg[i].e,edg[i].w);
}
for(int i=1;i<=m;i++){
build(i),spfa(2,dis2);
int v=edg[i].s;
f[v]=max(f[v],dis2[v]);
}
memset(fir,0,sizeof fir),cnt=0;
for(int i=1;i<=m;i++)
link(edg[i].e,edg[i].s,edg[i].w);
printf("%d\n",slove(2,1));
}
/*
3 3
1 2 1
1 3 2
3 2 3
*/
成绩:75
**分析:**f[i]的推理是正确的,但是在转换为dp[i]是没有考虑从其他点转换过来的情况/(ㄒoㄒ)/~~。
第二题:RGB rgb
题目描述:一个长度为n的字符串S,只包含RGB三种字母,给出一整数m,问多少四元组(a,b,c,d)满足a<=b<c<=d,S[a…b]+S[c…d]中至少包含m个连续的‘G’。
题解: A[i]表示i结尾的连续的‘G’串长度,B[i]表示i开头的连续的‘G’串长度。
P[i]表示以i结尾的至少包含m个连续的‘G’的字符串有多少个,A[i]>=m时,在i-m+1前面的位置都可以作为字符串的左端点,P[i]=i-m+1。A[i]<m时,i只能与前面的组合,P[i]=P[i]-1。
Q[i]表示以i开头的至少包含m个连续的‘G’的字符串有多少个,转移与P[i]思路类似。B[i]>=m时,Q[i]=n-(i+m-1)+1,B[i]<m时,Q[i]=Q[i+1]。
预处理完成后分成两种情况考虑,单独一个串就构成m以上连续的‘G’的情况,与两个串拼成连续超过m个‘G’的情况。
情况一:先对P求前缀和P[i]变为i以前的串(不一定以i结尾),对于每个位置,第一个字符串为连续‘G’,ans+=P[i]*C(n-i+1,2)。第二个字符串为连续‘G’,ans+=Q[i]*(C(i-1,2)+(i-1)-P[i-1]),减去P[i-1]是因为若第一个字符串连续超过m已经被算过了。
情况二:枚举第一个字符串长度至少为p,第二个必须刚好为q。枚举第一个的结尾i,A[i]>=p时 ans+=dp[i]=(i-p+1)-(P[i]-P[i-1](以i结尾的串)),枚举第二个的开头j,第二个字符串必须刚好为q所以B[i]>q时只能选前q个ans+=dp[i-1],当B[i]==q时还有(n-(i+q-1)+1-Q[i](去重))种选法。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10000+10;
int n,m;
long long A[N],B[N];
long long P[N],Q[N],dp[N],ans;
char str[N];
int C2(int n){
return n*(n-1)>>1;
}
int main(){
freopen("rgb.in","r",stdin);
freopen("rgb.out","w",stdout);
scanf("%s %d",str+1,&m);
n=strlen(str+1);
for(int i=1;i<=n;i++){
if(str[i]=='G')
A[i]=A[i-1]+1;
if(str[n-i+1]=='G')
B[n-i+1]=B[n-i+2]+1;
}
for(int i=1;i<=n;i++){
if(A[i]>=m) P[i]=i-m+1;
else P[i]=P[i-1];
ans+=P[i]*C2(n-i+1);
}
for(int i=1;i<=n;i++)
P[i]+=P[i-1];
for(int i=n;i>=1;i--){
if(B[i]>=m)
Q[i]=n-(i+m-1)+1;
else Q[i]=Q[i+1];
ans+=Q[i]*(C2(i-1)+(i-1)-P[i-1]);
}
for(int p=1;p<m;p++){
int q=m-p;
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];
if(A[i]>=p){
dp[i]+=(i-p+1);
dp[i]-=P[i]-P[i-1];
}
}
for(int i=n;i>=2;i--)if(B[i]>=q){
if(B[i]>q) ans+=dp[i-1];
else ans+=dp[i-1]*(n-(i+q-1)+1-Q[i]);
}
}
cout<<ans,putchar(10);
}