NOIP2017提高组 模拟赛23(总结)

NOIP2017提高组 模拟赛23(总结)

第一题 Hacker

【题目描述】
【解题思路】

  只需要两段假期,而且天数也不会超过10^6。直接用数组记下就好。排个序,遇头统计前面的是否有符合的,遇尾就把这段假期加进数组里。

【代码】
#include<cstdio>
#include<algorithm>

#define imin(a,b) ((a<b)?(a):(b))

using namespace std;

const int N=1000100;
const int oo=1000000100;
int n,m,n2,ans;
int L[N],R[N],X[N],mm[N];
struct data { int x,id; bool fl; } d[N<<1];

bool cmp(data A,data B) { return (A.x<B.x || (A.x==B.x && (A.fl==0 && B.fl==1))); }

int main()
{
    freopen("1982.in","r",stdin);
    freopen("1982.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=0;i<=m;i++) mm[i]=oo;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&L[i],&R[i],&X[i]);
        d[i].id=i; d[i].x=L[i]; d[i].fl=0;
        d[i+n].id=i; d[i+n].x=R[i]; d[i+n].fl=1;
    }
    n2=n<<1;
    sort(d+1,d+1+n2,cmp);
    ans=oo<<1;
    for(int i=1;i<=n2;i++)
    {
        int id=d[i].id;
        int len=R[id]-L[id]+1;
        if(d[i].fl==1) mm[len]=imin(mm[len],X[id]); else
        {
            if(len>=m) continue;
            if(mm[m-len]!=oo) ans=imin(ans,mm[m-len]+X[id]);
        }
    }
    printf("%d\n",(ans==(oo<<1))?(-1):(ans));
    return 0;
}

第二题 记字符串 (状压DP)

【题目描述】
【解题思路】

  字符串的长度和个数都不超过20。
  假如,要用第i位来使第i位相同的字符串独立,有两种情况:
  ①将所有相同的字符改成其他字符,最大的不用改。
  代价=所有第i位相同的字符串改第i位的代价的总和-字符串改第i位的最大代价。
  ②还有一种就是改单个字符。
  代价=最小的改一位的代价(从1~m位中改最小代价的那一位)。
  因为只有20个字符串,一定可以改成与其他字符不同的字符。
  状态压缩DP。
  记现在处理到第w位,已经独立的字符串的状态为S的最小代价。
  预处理出将第i个字符串的第j位改掉(用① 的改法),所影响的字符串集合Wij.S,代价为Wij.cost。
  还有第②中改法的单个字符串i最小代价mn[i]。
  找出S的未被独立的最小的i。
  ①枚举改第j位。 F[S|Wij.S]=min(F[S|Wij.S],F[S]+Wij.cost)
  ② F[S|(1<<(i1))]=min(F[S|(1<<(i1))],F[S]+mn[i])

【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

#define fn (1<<n)

using namespace std;

const int inf=1e9;
const int N=25;
const int big=1500000;
int n,m;
struct data { int stu,cs; } w[N][N];
int cs[N][N],d[N][N],f[big],mn[N];
char st[N];

int main()
{
    freopen("1983.in","r",stdin);
    freopen("1983.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",st);
        for(int j=0;j<m;j++) d[i][j+1]=st[j]-'a'+1;
    }
    for(int i=1;i<=n;i++)
    {
        mn[i]=inf;
        for(int j=1;j<=m;j++) scanf("%d",&cs[i][j]),mn[i]=imin(mn[i],cs[i][j]);
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            int ysum=0,mx=0;
            int stu=0;
            for(int h=1;h<=n;h++)
            if(d[h][j]==d[i][j])
            {
                stu+=(1<<(h-1));
                ysum+=cs[h][j];
                mx=imax(mx,cs[h][j]);
            }
            w[i][j].stu=stu;
            w[i][j].cs=ysum-mx;
        }
    }

    for(int i=1;i<fn;i++) f[i]=inf;
    f[0]=0;
    for(int i=0;i<fn;i++)
    if(f[i]!=inf)
    {
        int last=0;
        while(i&(1<<last)) last++;
        if(last>=n) continue;
        int yu;
        for(int j=1;j<=m;j++)
        {
            yu=i|(w[last+1][j].stu);
            f[yu]=imin(f[yu],f[i]+w[last+1][j].cs);
        }
        yu=i|(1<<(last));
        f[yu]=imin(f[yu],f[i]+mn[last+1]);
    }
    printf("%d\n",f[fn-1]);
    return 0;
}

第三题 士兵与旅行 (网络流)

【题目描述】
【解题思路】

  城市和道路构成一幅图,每一个城市的士兵只能去相邻的城市。一开始的状态为S1,结束状态为S2。
  先在需要从S1转到S2。
  可以对每一个城市建立两个点Ai,Bi,分别表示转移之前的和转移之后的。
  若城市i可以到达城市j,则Ai->Bj,流量无穷大。(因为士兵可以从i转移到j,流了多少就表示转移多少)
  从源点S->城市i,流量为一开始城市i的士兵数。
  城市i->汇点T,流量为转移之后的士兵数。
  判断流出流量是否等于流入流量就好。
  若等于,则一定存在转移方案。城市Ai->城市Bj的反向边的流量就是转移方案(因为记录的是剩余流量,开始为∞,流了w,变成∞-w。那么反向边的流量就是w)。

【代码】
#include<cstdio>
#include<algorithm>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))
#define ad(i) (i)
#define bd(i) (n+i)

using namespace std;

const int inf=1e9;
const int N=120;
const int M=220;
const int maxm=1000000;
int n,m,sum_a,sum_b;
int A[N],B[N];
int bfstime,q[maxm],lev[maxm],vis[maxm];
int h[maxm],d0,S,T;
int yu[N][N];
struct data { int val,to,ne; } d[maxm];

void addedge(int a,int b,int c)
{
    d0++;
    d[d0].to=b;d[d0].val=c;d[d0].ne=h[a];h[a]=d0;
    d0++;
    d[d0].to=a;d[d0].val=0;d[d0].ne=h[b];h[b]=d0;
}

bool bfs()
{
    int head=1,tail=1;
    q[head]=S;
    vis[S]=++bfstime;
    lev[S]=1;
    while(head<=tail)
    {
        for(int p=h[q[head]];p;p=d[p].ne)
        {
            if(!d[p].val) continue;
            int to=d[p].to;
            if(vis[to]==bfstime) continue;
            q[++tail]=to;
            vis[to]=bfstime;
            lev[to]=lev[q[head]]+1;
        }
        head++;
    }
    return vis[T]==bfstime;
}

int dfs(int now,int maxf)
{
    int ret=0,t;
    if(now==T || !maxf ) return maxf;
    for(int p=h[now];p;p=d[p].ne)
    {
        int to=d[p].to;
        if(!d[p].val || lev[to]!=lev[now]+1) continue;
        t=dfs(to,imin(maxf,d[p].val));
        d[p].val-=t;
        d[p^1].val+=t;
        maxf-=t;
        ret+=t;
    }
    if(maxf) lev[now]=-1;
    return ret;
}

int dinic()
{
    int ret=0;
    while(bfs()) ret+=dfs(S,1e9);
    return ret;
}

int main()
{
    freopen("1984.in","r",stdin);
    freopen("1984.out","w",stdout);
    scanf("%d%d",&n,&m); d0=1;
    S=(n<<1)+1; T=S+1;
    sum_a=sum_b=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&A[i]);
        addedge(S,ad(i),A[i]);
        sum_a+=A[i];
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&B[i]);
        addedge(bd(i),T,B[i]);
        addedge(ad(i),bd(i),inf);
        sum_b+=B[i];
    }
    for(int i=1;i<=m;i++)
    {
        int a,b; scanf("%d%d",&a,&b);
        addedge(ad(a),bd(b),inf);
        addedge(ad(b),bd(a),inf);
    }
    int sum_la=dinic();
    if(sum_la==sum_b)
    {
        printf("YES\n");
        for(int i=1;i<=n;i++)
        {
            int u=bd(i);
            for(int p=h[u];p;p=d[p].ne)
            {
                if(!(p&1)) continue;
                int to=d[p].to;
                yu[to][i]+=d[p].val;
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++) printf("%d ",yu[i][j]);
            printf("\n");
        }
    } else printf("NO\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值