BestCoder Round #75

A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5640

官方题解:

显然这很像求最大公约数的过程嘛,看这张神图:

https://i-blog.csdnimg.cn/blog_migrate/e6a44bbd615d12ef2e54dc0cb8f8a2d4.gif

所以每次 \gcdgcd 的时候累加答案即可,复杂度 O(\log\max(n, m)T)O(logmax(n,m)T)

当然你要是循环减应该也放过了。

我的思考:就是一直长的减短的,减到不能再减为止

#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=1010;
int main()
{
    int T,n,cnt,m,t;
    scanf("%d",&T);
    while(T--)
    {
        cnt=0;
        scanf("%d%d",&n,&m);
        if(n==m)
        {
            printf("1\n");
            continue;
        }
        while(n!=m)
        {
            if(n<m)
            {
                t=n;
                n=m;
                m=t;
            }
            if(m==0) break;
            cnt+=n/m;
            n%=m;
        }
        printf("%d\n",cnt);
    }
    return 0;
}


B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5641

官方链接:

一个简单的模拟题,首先判断序列长度是否合法,接着判断 s_isi 是否在 [1, 9][1,9],最后扫一遍看看有没有重复以及跨过中间点的情况即可。

复杂度:O(nT)O(nT)

我的思考:简单模拟,但我老是会出一些错误,导致WA,真是难受

#include<cstdio>
#include<cstring>
using namespace std;
int g[100][100];
void init()
{
    memset(g,0,sizeof(g));
    g[1][3]=g[3][1]=2;
    g[4][6]=g[6][4]=5;
    g[7][9]=g[9][7]=8;
    g[1][7]=g[7][1]=4;
    g[2][8]=g[8][2]=5;
    g[3][9]=g[9][3]=6;
    g[1][9]=g[9][1]=5;
    g[3][7]=g[7][3]=5;
}
int main()
{
    init();
    int T,i,flag,n,pre,vis[10],a[10];
    scanf("%d",&T);
    while(T--)
    {
        memset(vis,0,sizeof(vis));
        scanf("%d",&n);
        flag=1;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            if(a[i]<1||a[i]>9) flag=0;
        }
        if(!flag) {printf("invalid\n");continue;}
        if(n<4) {printf("invalid\n");continue;}
        pre=a[1];
        vis[a[1]]=1;
        for(i=2;i<=n;i++)
        {
            if(g[pre][a[i]]&&!vis[g[pre][a[i]]]) {flag=0;break;}
            if(vis[a[i]]) {flag=0;break;}
            vis[a[i]]=1;
            pre=a[i];
        }
        if(flag) printf("valid\n");
        else printf("invalid\n");
    }
    return 0;
}

C题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5642

官方题解:

数一个长度为 nn 的序列 , 并且序列中不能出现长度大于 33 的连续的相同的字符 , 这玩意就是一个数位DP嘛。 定义 d[i][j][k]d[i][j][k] 为处理完 ii 个字符 , 结尾字符为 'a'+ja+j , 结尾部分已重复出现了 kk 次的方案数。 刷表转移一下就好啦。

复杂度:O(26 * 26 * nT)O(2626nT)

我的理解:递推f[n][i]代表长度为n末尾重复个数为i的数的个数,那么当n>=3时 f[n][1]=sum(f[n-1][1]+f[n-1][2]+f[n-1][3])*25 f[n][2]=f[n-1][2] f[n][3]=f[n-1][3]

#include<cstdio>
using namespace std;
#define LL __int64
const LL mod=1000000007;
LL f[2005][4];
void init()
{
    int i,j;
    f[1][0]=f[1][1]=26;
    f[2][1]=26*25;
    f[2][2]=26;
    f[2][0]=f[2][1]+f[2][2];
    f[3][1]=26*26*25;
    f[3][2]=26*25;
    f[3][3]=26;
    f[3][0]=f[3][1]+f[3][2]+f[3][3];
    for(i=4;i<=2000;i++)
    {
        LL s=0;
        f[i][1]=f[i-1][0]*25%mod;
        for(j=2;j<=3;j++)
            f[i][j]=f[i-1][j-1];
        for(j=1;j<=3;j++)
            s=(s+f[i][j])%mod;
        f[i][0]=s;
    }
}
int main()
{
    init();
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        printf("%I64d\n",f[n][0]);
    }
    return 0;
}

D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643

官方题解:

约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。

我们看看裸的约瑟夫是怎么玩的:nn 个人,每隔 kk 个删除。

由于我们只关心最后一个被删除的人,并不关心最后的过程,所以,我们没有必要也不能够模拟整个过程。我们用递推解决。假设有nn个人围成环,标号为[0,n-1][0,n1]00开始的好处是取模方便),每数kk个人杀一个的情况下,最后一个存活的人的编号是f[n]f[n]

我们有f[1]=0f[1]=0,这不需要解释。

接着考虑一般情况f[n]f[n],第一个杀死的人的编号是k-1k1,杀死后只剩下n-1n1个人了,那么我们重新编号!

原来编号为k的现在是00号,也就是编号之间相差33我们只要知道现在n-1n1个人的情况最后是谁幸存也就知道nn个人的情况是谁幸存。幸运的是f[n-1]f[n1]已经算出来了那f[n]f[n]就是在f[n-1]f[n1]的基础上加上一个kk即可不要忘记总是要取模。

https://i-blog.csdnimg.cn/blog_migrate/dd08820945ffd7b3ef2924268079faad.png

所以递推式子是: f[i] = \begin{cases} & \text{ 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1] + k) mod i \ \ \ \ \ \ } other \end{cases}f[i]={ 0                      i=1 (f[i - 1] + k) mod i       other

此题只用在原版约瑟夫问题上加一维,由于依次隔 1, 2, 3...n - 11,2,3...n1 个人删除,所以用 f[i][j]f[i][j] 表示 ii 个人,依次隔 j, j + 1... j + i - 1j,j+1...j+i1 个人的幸存者标号。

根据刚才的重标号法,第一次 j - 1j1 号出局,从 jj 开始新的一轮,从 j + 1j+1 开始清除,剩余 i - 1i1 个人,也有递推式子:

f[i][j] = \begin{cases} & \text{ 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1][j+1] + j) mod i \ \ \ \ \ \ } other \end{cases}f[i][j]={ 0                      i=1 (f[i - 1][j+1] + j) mod i       other

答案就是 f[n][1] + 1f[n][1]+1(将标号转移到 [1, n][1,n]),问题轻松解决。

复杂度:预处理 O(n^2)O(n2),查询 O(1)O(1),总复杂度 O(n^2)O(n2)。由于可以滚动数组以及常数太小,所以 nn 给了 50005000(其实是出题人不会别的算法嘿嘿嘿)。


我的思考:约瑟夫问题主要是可以多往重新编号方面想,这样比较容易有思路

#include<cstdio>
using namespace std;
const int N=5005;
int dp[N];
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        dp[1]=0;
        scanf("%d",&n);
        for(int i=2;i<=n;i++)
        {
            dp[i]=(dp[i-1]+n-i+1)%i;
        }
        dp[n]+=1;
        printf("%d\n",dp[n]);
    }
    return 0;
}

E题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5644

官方题解:

这是一个费用流建模题 , BC好久都没有费用流题了……

这个题关键是要满足每天的飞行员数量充足 , 那么可能是一开始就有的飞行员 , 或者某飞行员休假归来 , 或者新招募的飞行员。

将每一天拆成两个点 ,分别表示这一天开始时候飞行员的状态和这一天工作完成后飞行员的状态 , 分别为 X_iXi , Y_iYi

先看看建图:

  1. 从 S 向每个 X_iXi 连一条容量为 P_iPi,费用为 00 的有向边。
  2. 从每个 Y_iYi向 T 连一条容量为 P_iPi,费用为 00 的有向边。
  3. 从 S 向 Y_1Y1 连一条容量为 kk , 费用为 00 的有向边。
  4. 从 S 向每个 Y_i(i\ge P)Yi(iP) 连一条容量为无穷大,费用为 QQ 的有向边。(新招募的)
  5. 从每个 XiXi 向 Xi+1Xi+1 连一条容量为无穷大,费用为 00 的有向边。
  6. 从每个YiYiYi+1Yi+1连一条容量为无穷大,费用为 00 的有向边。
  7. 对于每一个休假计划 , 从每一个X_iXi 向 Y_{i+T_i}Yi+Ti 连一条容量为无穷大费用为 S_iSi 的有向边

然后开心的跑一跑最小费用最大流看一看是不是满流就可以判断是否可行啦……


我的思考:想是想不到,但是看到题解能理解,应该是网络流题目做太少了。最小费用最大流

#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
const int N=2005;
const int M=200005;
int dist[N],pre[N],head[N];
bool vis[N];
int tot,ans,cnt;
struct node
{
    int v,cap,next,cost;
}edge[M];
void addedge(int u,int v,int w,int cost)
{
    edge[tot].v=v;
    edge[tot].cap=w;
    edge[tot].cost=cost;
    edge[tot].next=head[u];
    head[u]=tot++;

    edge[tot].v=u;
    edge[tot].cap=0;
    edge[tot].cost=-cost;
    edge[tot].next=head[v];
    head[v]=tot++;
}
queue<int>q;
bool spfa(int s,int t)
{
    while(!q.empty())
        q.pop();
    for(int i=s;i<=t;i++)
    {
        dist[i]=inf;
        vis[i]=false;
        pre[i]=-1;
    }
    q.push(s);
    dist[s]=0;
    vis[s]=true;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next) {
            int v=edge[i].v;
            int cost=edge[i].cost;
            if(edge[i].cap&&dist[v]>dist[u]+cost) {
                dist[v]=dist[u]+cost;
                pre[v]=i;
                if(!vis[v]) {
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    if(dist[t]==inf) return false;
    return true;
}
void addmaxflow(int t)
{
    int i,maxn=inf;
    for(i=pre[t];i!=-1;i=pre[edge[i^1].v]) maxn=min(maxn,edge[i].cap);
    for(i=pre[t];i!=-1;i=pre[edge[i^1].v])
    {
        edge[i].cap-=maxn;
        edge[i^1].cap+=maxn;
    }
    ans+=dist[t]*maxn;
    cnt+=maxn;
}
void maxflow(int s,int t)
{
    while(spfa(s,t)) addmaxflow(t);
}
int p[N];
int a[6],b[5],T,n,k,i,m,P,Q;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        tot=ans=cnt=0;
        memset(head,-1,sizeof(head));
        scanf("%d%d",&n,&k);
        int sum=0,s=0,t=2*n+1;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&p[i]);
            sum+=p[i];
            addedge(s,i,p[i],0);
            addedge(i+n,t,p[i],0);
        }
        scanf("%d%d%d",&m,&P,&Q);
        for(int i=1;i<=m;i++)
            scanf("%d%d",&a[i],&b[i]);
        addedge(s,1+n,k,0);
        for(int i=P;i<=n;i++) addedge(s,i+n,inf,Q);
        for(int i=n+1;i<2*n;i++) addedge(i,i+1,inf,0);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
                if(i+b[j]<=n)
                    addedge(i,i+n+b[j],inf,a[j]);
        }
        maxflow(s,t);
        if(cnt==sum) printf("%d\n",ans);
        else printf("No solution\n");
    }
    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值