The 2017 ACM-ICPC Asia Beijing Regional (赛后整理)

本文是2017 ACM-ICPC Asia Beijing Regional的比赛赛后整理,重点解析了涉及几何和区间DP的题目E - Cats and Fish、F - Secret Poems、G - Liaoning Ship’s Voyage和J - Pangu and Stones。E题和F题相对较简单,使用优先队列和斜向填充即可解决。G题是涉及线段多边形相交的BFS问题,而J题则提出了一个关于石子合并的复杂DP问题。
摘要由CSDN通过智能技术生成

The 2017 ACM-ICPC Asia Beijing Regional (赛后整理)

PS: E,F是个大水题,但是因为队伍英语水平不高(菜是原罪),E题队友40分钟才A,我去读F题,发现是个水题,20分钟后又把F题A了,之后 J 题读错题意(successive 读成“成功的”,导致理解错题意,爆搜搞答案,当然是一直tle或wa到底)G题读完发现是个sb题,bfs+线段多边形规范相交就行,wa到底,比赛结束后发现我才是sb,G题坐标都没转换正确,线段多边形规范相交还有一种情况没判断。H题读完题意,发现不会。

比赛结束就A了2道,…好菜啊

不过这次的几何G题和 区间dp J 题不错。

E - Cats and Fish

思路:这道题用优先队列或者set模拟一下即可,太水就鸽了吧(本来很早就要写这题,不过没时间…拖到现在也不想搞了)

F - Secret Poems

思路:斜着一遍把原字符串还原,然后蛇形填数形成新的grid即可。

水题

代码:

#include<bits/stdc++.h>
#define  mset(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=1e2+10;
char g[N][N];
char a[N][N];
string s;
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; ++i) scanf("%s",g[i]+1);
        s="";
        for(int i=1; i<=n; ++i)
        {
            if(i&1)//向上
            {
                for(int x=i,y=1; x>=1; x--,y++)
                    s+=g[x][y];
            }
            else
            {
                for(int x=1,y=i; y>=1; x++,y--)
                    s+=g[x][y];
            }
        }
        for(int i=2; i<=n; ++i)
        {
            if((n+i-1)&1)//向上
            {
                for(int x=n,y=i; y<=n; y++,x--)
                    s+=g[x][y];
            }
            else
            {
                for(int x=i,y=n; x<=n; x++,y--)
                    s+=g[x][y];
            }
        }
        mset(a,0);
        int k=0;
        a[1][1]=s[k++];
        int x=1,y=1;
        int mx=n*n;
        while(k < mx)
        {
            while(k < mx&&y < n&&a[x][y+1]==0)//向前走y++;
            {
                y++;
                a[x][y]=s[k++];
            }
            while(k<mx && x<n &&a[x+1][y]==0)//向下走  x++;
            {
                x++;
                a[x][y]=s[k++];
            }
            while(k<mx && y > 1&&a[x][y-1]==0)//向左走   y--;
            {
                y--;
                a[x][y]=s[k++];
            }
            while(k<mx &&x>1 && a[x-1][y] ==0)//向上走   x--
            {
                x--;
                a[x][y]=s[k++];
            }
        }
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
                printf("%c",a[i][j]);
            puts("");
        }
    }
    return 0;
}

G - Liaoning Ship’s Voyage

题意:给出一个n*n的格子,和一个三角形,开始自己在左下角(0,0),现要去(n-1,n-1)坐标。每次可以走周围的8个方向,不能走’#’,每走一次的路线是直线,要求走的路线(线段)不能穿过三角形,但可以在三角形边上走。问走的最小步数。 n ∈ [ 1 , 20 ] n\in[1,20] n[1,20]

思路:bfs+线段多边形规范相交吧,在输入坐标方面注意x轴是输入的列,y轴是输入的行,需要转化下。判断线段多边形不规范相交,首先线段与多边形的所有边都不规范相交,其次两个点都不在多边形内,但可能出现两个点在多边形边上,但中间的线段穿过多边形,这时可以在多边形上取几百个点判断下即可。

代码:

#include<bits/stdc++.h>
#define  mset(a,b) memset(a,b,sizeof(a))
using namespace std;
double xx[10],yy[10];
double const eps=1e-8;
char gg[25][25],g[25][25];
int s[25][25];
int n;
int dir[8][2]= {0,1,0,-1,1,0,-1,0,1,1,1,-1,-1,1,-1,-1};
struct node
{
    int x,y,s;
    node() {}
    node(int x,int y,int s):x(x),y(y),s(s) {}
};
int sgn(double x)
{
    if(abs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
double add(double a,double b)
{
   if(abs(a+b)<eps*(abs(a)+abs(b))) return 0;
    return a+b;
}
struct Point
{
    double x,y;
    Point() {}
    Point(double x,double y):x(x),y(y) {}
    Point operator -(Point p)
    {
        return Point(add(x,-p.x),add(y,-p.y));
    }
    Point operator +(Point p)
    {
        return Point(add(x,p.x),add(y,p.y));
    }
    double operator ^(Point p)
    {
        return add(x*p.y,-y*p.x);
    }
    Point operator *(double d)
    {
        return Point(x*d,y*d);
    }
    double operator *(Point p)
    {
        return add(x*p.x,y*p.y);
    }
} avg[3];
struct Line
{
    Point s,e;
    Line() {}
    Line(Point s,Point e):s(s),e(e) {}
} line[10];
int getDirPPP(Point p,Point p1,Point p2)
{
    return sgn((p1-p)^(p2-p));
}
bool onSge(Line l,Point q)
{
    return ((l.s-q)^(l.e-q))==0&&((l.s-q)*(l.e-q)) <=0;
}
bool isInterSS(Line la,Line lb)//判断规范相交
{
    int d1=getDirPPP(lb.s,lb.e,la.s);
    int d2=getDirPPP(lb.s,lb.e,la.e);
    int d3=getDirPPP(la.s,la.e,lb.s);
    int d4=getDirPPP(la.s,la.e,lb.e);
    if(d1*d2<0&&d3*d4<0)
        return true;
    return  false;
}
bool notinari(Point a)//点在三角形内返回flase//边上返回true
{
    for(int i=0; i<3; ++i)
        if(getDirPPP(a,avg[i],avg[(i+1)%3])==0) return true;
    if(getDirPPP(a,avg[0],avg[1]) < 0&& getDirPPP(a,avg[1],avg[2])<0 && getDirPPP(a,avg[2],avg[0])<0) return false;
    if(getDirPPP(a,avg[0],avg[1]) > 0&& getDirPPP(a,avg[1],avg[2])>0 && getDirPPP(a,avg[2],avg[0])>0) return false;
    return true;

}
bool judge(Line a)
{
    for(int i=0; i<3; ++i)
        if(isInterSS(a,line[i]))
            return false;
    double d=0.01,s=1;
    for(int i=0; i<100; ++i)
    {
        Point t=a.e+(a.s-a.e)*s;
        if(!notinari(t)) return false;
        s-=d;
    }
    if(notinari(a.s)&&notinari(a.e))
        return true;
    else
        return false;
}
int work()//下标从0开始
{
    mset(s,-1);
    s[0][0]=0;
    queue<node> Q;
    Q.push(node(0,0,0));
    while(!Q.empty())
    {
        node o=Q.front();
        Q.pop();
        for(int i=0; i<8; ++i)
        {
            int nx=o.x+dir[i][0];
            int ny=o.y+dir[i][1];

            if(nx>=0&&nx<n&&ny>=0&&ny<n&&g[nx][ny]=='.'&&s[nx][ny]==-1)
            {
                if(judge(Line(Point(1.0*nx,1.0*ny),Point(1.0*o.x,1.0*o.y))))
                {
//                    printf("--nx:%d,ny:%d\n",nx,ny);
                    Q.push(node(nx,ny,o.s+1));
                    s[nx][ny]=o.s+1;
                }
            }
        }
    }
    return s[n-1][n-1];
}
int main()
{

    while(~scanf("%d",&n))
    {
        for(int i=0; i<3; ++i)
        {
            scanf("%lf%lf",&xx[i],&yy[i]);
            avg[i]=Point(xx[i],yy[i]);
        }

        int top=0;
        for(int i=0; i<3; ++i)
            for(int j=i+1; j<3; ++j) //3
                line[top++]=Line(Point(xx[i],yy[i]),Point(xx[j],yy[j]));
        for(int i=n-1; i >=0; --i)
            scanf("%s",gg[i]);
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                g[i][j]=gg[j][i];
        cout<<work()<<endl;
    }
    return 0;
}

H - Puzzle Game

题意:给出一个n*m的矩形和一个整数p,可以选择在矩形中选择一个数将之变成p,要求最后的最大子矩阵和最小。 n , m ∈ [ 1 , 150 ] , a i , j , p ∈ [ − 1000 , 1000 ] n,m\in[1,150],a_{i,j},p\in[-1000,1000] n,m[1,150],ai,j,p[1000,1000]

思路:不会

J - Pangu and Stones

题意:给出n个石子和石子的重量,每次可以选择L到R个石子合并成一个,花费为合并的石子的重量,求合并成一堆的最小花费,不能合成一堆输出0。 n ∈ [ 2 , 100 ] , 2 ≤ l ≤ r ≤ n ​ n\in[2,100],2\le l\le r\le n​ n[2,100],2lrn

思路

这题感觉很有意思,我们用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示状态,但是这个状态的含义有些丰富(

k = 1 ​ k=1​ k=1时, d p [ i ] [ j ] [ 1 ] ​ dp[i][j][1]​ dp[i][j][1]表示 i ​ i​ i j ​ j​ j 的石子合并成一堆所需的最小花费。

k > 1 ​ k>1​ k>1时, d p [ i ] [ j ] [ k ] ​ dp[i][j][k]​ dp[i][j][k]表示 i ​ i​ i j ​ j​ j 的石子划分成 k ​ k​ k堆的最小花费,注意这里的划分是还没有合并,只是划分。

转移方程:

d p [ i ] [ j ] [ 1 ] = m i n ( d p [ i ] [ j ] [ 1 ] , d p [ i ] [ j ] [ k ] + s u m [ i ] [ j ] ) , k ∈ [ l , r ] ​ dp[i][j][1]=min(dp[i][j][1],dp[i][j][k]+sum[i][j]),k\in[l,r]​ dp[i][j][1]=min(dp[i][j][1],dp[i][j][k]+sum[i][j]),k[l,r]。注意这里的 s u m [ i ] [ j ] ​ sum[i][j]​ sum[i][j]表示 i ​ i​ i j ​ j​ j 的总重量,这个状态转移表示的是一次合并操作,

k > 1 ​ k>1​ k>1时, d p [ i ] [ j ] [ k ] = m i n ( d p [ i ] [ j ] [ k ] , d p [ i ] [ c ] [ 1 ] + d p [ c + 1 ] [ j ] [ k − 1 ] ) , c ∈ [ i , j − k + 1 ] ​ dp[i][j][k]=min(dp[i][j][k],dp[i][c][1]+dp[c+1][j][k-1]),c\in[i,j-k+1]​ dp[i][j][k]=min(dp[i][j][k],dp[i][c][1]+dp[c+1][j][k1]),c[i,jk+1] ,这里表示的是划分操作。

代码

#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int N=105;
const int inf=0x3f3f3f3f;
int a[N],sum[N];
int dp[N][N][N];
int main()
{
    int n,L,R;
    while(~scanf("%d%d%d",&n,&L,&R))
    {
        for(int i=1;i<=n;++i) scanf("%d",a+i);
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=n; ++j)
                for(int k=1; k<=R; ++k) dp[i][j][k]=inf;
        for(int i=1; i<=n; ++i) sum[i]=sum[i-1]+a[i];
        for(int i=1; i<=n; ++i) dp[i][i][1]=0;
        for(int ls=2; ls<=n; ++ls)
        {
            for(int l=1; l+ls-1 <= n; ++l)
            {
                int r=l+ls-1;
                //先处理k\in[2,R]的情况
                for(int k=2; k<=R; ++k) //dp    r-c>=k-1  c<=r-k+1
                {
                    for(int c=l; c<=r-k+1; ++c) //dp[l][c][1] +dp[c+1][r][k-1]
                        dp[l][r][k]=min(dp[l][r][k],dp[l][c][1]+dp[c+1][r][k-1]);
                }
                //处理k=1的情况
                for(int k=L; k<=R; ++k)
                    dp[l][r][1]=min(dp[l][r][1],dp[l][r][k]+sum[r]-sum[l-1]);
            }
        }
        if(dp[1][n][1]==inf)
            printf("0\n");
        else
            printf("%d\n",dp[1][n][1]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值