一道模拟赛的题

前言

这是一个不错的题啊,在这里记录一下

题意

听说不是原创题,那我就放上来了。。应该没有关系吧QAQ
有一个 n × m 的地图, 地图上的每一个位置可以是空地, 炮塔或是敌人. 你需要操纵炮塔消灭敌人.对于每个炮塔都有一个它可以瞄准的方向, 你需要在它的瞄准方向上确定一个它的攻击位置,当然也可以不进行攻击. 一旦一个位置被攻击, 则在这个位置上的所有敌人都会被消灭.保证对于任意一个炮塔, 它所有可能的攻击位置上不存在另外一个炮塔.定义炮弹的运行轨迹为炮弹的起点和终点覆盖的区域. 你需要求出一种方案, 使得没有两条炮弹轨迹相交.求消灭最多的敌人

题解

首先,你要注意到保证对于任意一个炮塔, 它所有可能的攻击位置上不存在另外一个炮塔.这个条件。我当时就没有注意到QAQ
语文太差了
考场的时候没有想出来啊
但总感觉是一种网络流之类的东西,但是不知道怎么建图
这里提供两种建图方法,都很秒啊

第一种方法

这种方法在随机数据上表现良好,但是可以被人为构造卡掉
但是卡掉的时候也不是特别慢。。但是思路还是很妙的

我们考虑到,如果把向行发射的炮塔看做是A点,向列发射的炮塔看做是B的点。
那么显然地,只有A和B会互相影响
A和A是不会影响的
于是我们可以把他看做一个二分图
但是有一个问题,那就是,对于每一个炮塔,他只可以打他最大的一个,二不能打多个
那么A自己本身对自己也有限制,那怎么办呢?
我们知道,如果我们暴力这么建图,那么最后的答案,是A点可以打连续一段的答案,这不是我们想要地
我们考虑到,对于一个A,如果他打得远,那么肯定是为了更大的代价,因此,他可以决策的地方的值肯定是单调上升的
我们可以吧这些点找出来
然后对于这些点的权值差分一下,就可以完美地解决问题了
然后剩下怎么做,你就把x打到y看做一个点就可以了
直接最小割上就好了
但是这个方法点数和边数可以被卡的很大

CODE:

#include<cstdio>
#include<queue>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int MAX=1<<30;
const int N=55;
int n,m;
int a[N][N];
struct qt
{
    int x,y,xx,yy,z;//这个节点可以代表的权值是什么 
}s[2][5005*2];//每一个点的信息
struct qq
{
    int x,y,last,z;
}e[5005*5005];int num,last[5005*2];
int st,ed;
void init (int x,int y,int z)
{
//  printf("%d %d %d\n",x,y,z);
    num++;
    e[num].x=x;e[num].y=y;e[num].z=z;
    e[num].last=last[x];
    last[x]=num;

    num++;
    swap(x,y);z=0;
    e[num].x=x;e[num].y=y;e[num].z=z;
    e[num].last=last[x];
    last[x]=num;
}
int tot[2];
int X[4]={-1,1,0,0};
int Y[4]={0,0,-1,1};
bool ok (int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
bool check (qt y,qt x)//x是竖着的,y是横着的 
{
    bool tf=true;
    tf&=(max(x.x,x.xx)>=y.x&&min(x.x,x.xx)<=y.x);
    tf&=(max(y.y,y.yy)>=x.y&&min(y.y,y.yy)<=x.y);
    return tf;
}
int h[5005*2];
bool Bt ()
{
    memset(h,-1,sizeof(h));h[st]=1;
    queue<int> q;
    q.push(st);
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int u=last[x];u!=-1;u=e[u].last)
        {
            int y=e[u].y;
            if (e[u].z>0&&h[y]==-1)
            {
                h[y]=h[x]+1;
                q.push(y);
            }
        }
    }
    return h[ed]!=-1;
}
int dfs (int x,int f)
{
    if (x==ed) return f;
    int s1=0;
    for (int u=last[x];u!=-1;u=e[u].last)
    {
        int y=e[u].y;
        if (e[u].z>0&&h[y]==h[x]+1&&s1<f)
        {
            int lalal=dfs(y,min(e[u].z,f-s1));
            s1+=lalal;
            e[u].z-=lalal;
            e[u^1].z+=lalal;
        }
    }
    if (s1==0) h[x]=-1;
    return s1;
}
int main()
{
    freopen("cti.in","r",stdin);
    freopen("cti.out","w",stdout);
    num=1;memset(last,-1,sizeof(last));
    scanf("%d%d",&n,&m);
    for (int u=1;u<=n;u++)
        for (int i=1;i<=m;i++)
            scanf("%d",&a[u][i]);
    for (int u=1;u<=n;u++)
        for (int i=1;i<=m;i++)
            if (a[u][i]<0)//如果这是一个炮台 
            {
                int tt=(-a[u][i])-1;
                int o=(tt<=1);
                int x=u+X[tt],y=i+Y[tt];
                int last=0;
                while (ok(x,y))
                {
                    if (a[x][y]>last)
                    {
                        tot[o]++;
                        s[o][tot[o]].x=x;
                        s[o][tot[o]].y=y;
                        s[o][tot[o]].xx=u;
                        s[o][tot[o]].yy=i;
                        s[o][tot[o]].z=a[x][y]-last;
                        last=a[x][y];
                    }
                    x=x+X[tt];y=y+Y[tt];
                }
            }
    st=tot[0]+tot[1]+1,ed=st+1;
    int sum=0;
    for (int u=1;u<=tot[0];u++) 
    {
        init(st,u,s[0][u].z);
        sum=sum+s[0][u].z;
    }
    for (int u=1;u<=tot[1];u++) 
    {
        init(u+tot[0],ed,s[1][u].z);
        sum=sum+s[1][u].z;
    }
    for (int u=1;u<=tot[0];u++)
        for (int i=1;i<=tot[1];i++)
        {
            if (check(s[0][u],s[1][i]))
            {
                init(u,i+tot[0],MAX);
            }
        }
    while (Bt()) sum=sum-dfs(st,MAX);
    printf("%d\n",sum);
    return 0;
}

第二种方法

这个构图方法就不怕被卡了,思路也很好啊
考虑最小割模型.
考虑分别对横着的和竖着的炮, 把它们覆盖的点连成一条链 (当然直接跑网络流求的是最小值,需要用一个大数减去).如何体现” 不能相交” 的限制呢?
我们将先将横着的链建好, 再反着建竖着的链, 最后将每个相交的链之间连一条无穷大边, 这样就可以保证求出来的最小割是合法的了.
这个的话,理解起来就是我们所要知道的是每个炮台打到哪一个点停下来
一个是从ed开始往前看的
一个是从st开始忘后看的
如果停的位置跨过了别的炮台的轨道,那么别的轨道就要在这个点之前停下来了
这样的话,建图也很显然了
并且点数和边数都是很少的

Update4.8
复习这个题的时候发现代码好像不是我想的啊。。
可能是我贴错了
但是现在我当时bac的代码已经没了。。
贴一个唐队队的吧
这个保证是对的,我仔细看了一下,和我记忆中的做法是一样的
CODE:

#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int inf=(1<<30);
const int Maxn=6000;
int n,m,n1[55][55],n2[55][55],c1=0,mp[55][55];
int mark[55][55];
struct Edge{int y,d,next;}e[2110000];
int len=1,last[Maxn];
void ins(int x,int y,int d)
{
//  printf("%d %d %d\n",x,y,d);
    int t=++len;
    e[t].y=y;e[t].d=d;
    e[t].next=last[x];last[x]=t;
}
void addedge(int x,int y,int d){ins(x,y,d),ins(y,x,0);}
int st,ed;
int h[Maxn];
bool bfs()
{
    memset(h,0,sizeof(h));h[st]=1;
    queue<int>q;q.push(st);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=last[x];i;i=e[i].next)
        {
            int y=e[i].y;
            if(e[i].d&&!h[y])h[y]=h[x]+1,q.push(y);
        }
    }
    return h[ed];
}
int dfs(int x,int f)
{
    if(x==ed)return f;
    int s=0,t;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(h[y]==h[x]+1&&e[i].d>0&&s<f)
        {
            t=dfs(y,min(f-s,e[i].d));
            s+=t;e[i^1].d+=t;e[i].d-=t;
        }
    }
    if(s==0)h[x]=0;
    return s;
}
int main()
{
    int ans=0;
    scanf("%d%d",&n,&m);
    st=++c1,ed=++c1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    scanf("%d",&mp[i][j]),n1[i][j]=++c1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(mp[i][j]==-1)
        {
            addedge(n1[i][j],ed,1000);ans+=1000;
            for(int k=i-1;k;k--)
            addedge(n1[k][j],n1[k+1][j],1000-mp[k][j]),mark[k][j]=1;
            addedge(st,n1[1][j],inf);
        }
        else if(mp[i][j]==-2)
        {
            addedge(n1[i][j],ed,1000);ans+=1000;
            for(int k=i+1;k<=n;k++)
            addedge(n1[k][j],n1[k-1][j],1000-mp[k][j]),mark[k][j]=2;
            addedge(st,n1[n][j],inf);
        }
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    n2[i][j]=++c1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(mp[i][j]==-4)
        {
            addedge(st,n2[i][j],1000);ans+=1000;
            for(int k=j+1;k<=m;k++)
            {
                addedge(n2[i][k-1],n2[i][k],1000-mp[i][k]);
                if(mark[i][k]==2)addedge(n2[i][k-1],n1[i-1][k],inf);
                else if(mark[i][k]==1)addedge(n2[i][k-1],n1[i+1][k],inf);
            }
            addedge(n2[i][m],ed,inf);
        }
        else if(mp[i][j]==-3)
        {
            addedge(st,n2[i][j],1000);ans+=1000;
            for(int k=j-1;k;k--)
            {
                addedge(n2[i][k+1],n2[i][k],1000-mp[i][k]);
                if(mark[i][k]==2)addedge(n2[i][k+1],n1[i-1][k],inf);
                else if(mark[i][k]==1)addedge(n2[i][k+1],n1[i+1][k],inf);
            }
            addedge(n2[i][1],ed,inf);
        }
    }
    while(bfs())ans-=dfs(st,inf);
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值