20170825考试总结

第一题:传递情报 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值