2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)

Time:2018.4.15   13:00-18:00

Link


A - Alphabet   solved by czh&ym             

题意

给一个字符串,问最少添加几个字符使得它能够存在一个子序列“abcdefg…xyz”。长度不超过50

分析

ym:求一个最大上升子序列即可


B - Buggy Robot     solve  by ym&czh

题意

大概就是写一个up,down,left,right的指令,操控小机器人走出迷宫,然后如果机器人遇到的这条指令,是让它走到障碍物上的,它会跳过这条指令。如果机器人到达了终点,则所有剩余指令失效。你可以通过增加和删除指令,来使得机器人走到中间,问你至少需要修改几个指令。迷宫的大小50*50。指令的长度是50

分析

ym:定义:dp[i][j][k]:当前位于点(i,j),即将执行第k条指令需要增加的最小指令数,状态数:50*50*50=125*1000  完全ok,转移:考虑BFS暴力转移,同一个点的一种状态只能出发一次

#include<bits/stdc++.h>
using namespace std;

const int maxn = 50+3;
const int INF=0x3f3f3f3f;
const int dx[]={0,0,-1,1};
const int dy[]={-1,1,0,0};

char mp[maxn][maxn];
int sx,sy;
char cmd[maxn];
int len;
int n,m;
int dp[maxn][maxn][maxn];
int answer;

struct node{
    int x,y,k;
}d[maxn];

bool ok(int x,int y){
    if(((x>=0)&&x<n)&&((y>=0)&&y<m))
    return 1;
    return 0;
}

int hs(char c)
{
    if(c=='L') return 0;
    if(c=='R') return 1;
    if(c=='U') return 2;
    if(c=='D') return 3;
}

queue<node>q;
void bfs()
{
    int fx;
    q.push({sx,sy,0});
    dp[sx][sy][0]=0;
    while(!q.empty())
    {
        node now=q.front();
        q.pop();
        if(mp[now.x][now.y]=='E')
        {
            answer=min(answer,dp[now.x][now.y][now.k]);
            continue;
        }
        for(int i=0;i<4;i++)
        {
            int xx=now.x+dx[i],yy=now.y+dy[i];
            if(!ok(xx,yy)||mp[xx][yy]=='#')
            {
                if(now.k<len)
                fx=hs(cmd[now.k]);
                else
                    fx=-1;
                if(fx==i)
                {
                    if(dp[now.x][now.y][now.k+1]>dp[now.x][now.y][now.k])
                    {
                        if(dp[now.x][now.y][now.k+1]==INF)
                        q.push({now.x, now.y, now.k+1});
                        dp[now.x][now.y][now.k+1]=dp[now.x][now.y][now.k];
                    }
                }
            }
            else
            {
                    if(now.k<len)
                    fx=hs(cmd[now.k]);
                    else
                    fx=-1;
                    if(fx==i)
                    {
                        if(dp[xx][yy][now.k+1]>dp[now.x][now.y][now.k])
                        {

                         if(dp[xx][yy][now.k+1]==INF)
                         q.push({xx, yy, now.k+1});
                         dp[xx][yy][now.k+1]=dp[now.x][now.y][now.k];
                        }
                    }
                    else
                    {
                        int last=dp[now.x][now.y][now.k]+1;
                        if(dp[xx][yy][now.k]>last)
                        {
                            if(dp[xx][yy][now.k]==INF)
                            q.push({xx, yy, now.k});
                            dp[xx][yy][now.k]=last;
                        }
                    }
            }
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%s", mp[i]);
        for(int j=0;j<m;j++)
        {
            if(mp[i][j]=='R')
            {
                sx=i,sy=j;
            }
        }
    }
    answer=INF;
    scanf("%s", cmd);
    len=strlen(cmd);
    memset(dp,INF,sizeof(dp));
    bfs();
    printf("%d\n", answer);
    return 0;
}

C - Cameras        solved by czh&ym

题意

数轴上1-n,你已经在其中k个位置有了标记,问你至少需要添加几个标记,使得任意连续r个位置,都至少有diff = 2个标记。2n100000,0kn,2rn 

分析

单调队列问题

ym:处理好区间[1,r],从左往右的贪心的移动即可,因为移动一个仅能变化一个值,所以对于<=r的数量的标记都可以做


D

推组合数公式较难,组合数问题


E

题意

给定 n个点,已知选定 k个点,再多选一个点,使得 k+1 个点的凸包最大。

分析

 


F - Illumination          solved by ym

题意

给你一个n*n的方格,里面有l个方格有灯,每个灯可以选择左右发射长度r格的光或者上下发射,问你是否有一种方案,使得每个格子,不会被同为纵向(横向)的多束光照射。n100

分析

ym:已填坑,2-SAT模板题确认无误(前置技能:Tarjan求联通分量)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9+7;
const int maxn = 2000+7;

stack<int>s;
bool vis[maxn];
int dfn[maxn],low[maxn],scc[maxn],idx;
vector<int>g[maxn];

int n,r,l;
int x[maxn],y[maxn];


void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    s.push(u);
    vis[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i){
        v=g[u][i];
        if(dfn[v]==0){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }else if(vis[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
        do{
            v=s.top();
            s.pop();
            scc[v]=u;//指向根节点
            vis[v]=false;
        }while(u!=v);
}

int _abs(int x)
{
    if(x>0)return x;
    return -x;
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>r>>l;
    for(int i=0;i<l;i++)
    cin>>x[i]>>y[i];
    for(int i=0;i<l;i++)
    {
        for(int j=i+1;j<l;j++)
        {
            if((x[i]==x[j]) && _abs(y[i]-y[j])<=2*r)  g[2*j].push_back(2*i+1),g[2*i].push_back(2*j+1);
            if(y[i]==y[j] && _abs(x[i]-x[j])<=2*r)  g[2*j+1].push_back(2*i),g[2*i+1].push_back(2*j);
        }
    }
    for(int i=0;i<2*l;i++)
    {
        if(!dfn[i])
            tarjan(i);
    }
    bool flag=false;
    for(int i=0;i<l;i++)
    {
        if(scc[i*2]==scc[2*i+1])
            flag=true;
    }
    if(flag)
        cout<<"NO"<<endl;
    else
        cout<<"YES"<<endl;
    return 0;
}

G - Maximum Islands   solved by ym

题意

给你一个50*50 的图,里面有LWC三种字符,L代表陆地,W代表水,C代表可以由你决定是水还是陆地,问你这个图中L的联通块最多有几块 

分析

ym:已填坑,首先很直观的每个L周围都应该是W(预处理),对于剩下的所有C,肯定一个L的联通块只有一个L最优,剩下的是W,所以划分为两个点集,显然不相邻的C肯定可以状态相同((x+y)%2相同的点),对每个状态为((x+y)%2相同的点)C点周围的C点建边,转化为最大独立集问题,跑一个二分图即可,注意由于将所有点都跑了二分图,最后的最大匹配要 /2 

二分图的建立:
对于一个点[x,y],和它连通的四个点的x+y的奇偶性是和它相反的,所以我们可以利用这个性质去建立二分图,把x+y为奇数的放一边,为偶数的放另一边,相邻的建边,以此建立二分图

Trick:建的图满足二分图:因为状态相同((x+y)%2)的点的集合之间没有边,与其相邻的点都在另一个点集里

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};

const int maxn = 80;

int tot,head[maxn*maxn],nxt[maxn*maxn],to[maxn*maxn];

int boy[maxn*maxn],used[maxn*maxn];

char mp[maxn][maxn];
int n, m, ans, cnt,vis[maxn][maxn];
int xy[maxn][maxn];


void add(int u,int v){
    to[++tot]=v;
    nxt[tot]=head[u];
    head[u]=tot;
}

bool found(int x){
    for(int i=head[x]; i!=0; i=nxt[i]){
        int u=to[i];
        if(!used[u]){
            used[u]=1;
            if(boy[u]==0 || found(boy[u])){
                boy[u]=x;
                return 1;
            }
        }
    }
    return 0;
}

bool ok(int x,int y)
{
    if(x>=1&&x<=n&&y>=1&&y<=m)
        return 1;
    return 0;
}

void dfs(int x,int y)
{
    vis[x][y]=1;
    for(int i=0;i<4;i++)
    {
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(ok(xx,yy)&&!vis[xx][yy])
        {
            if(mp[xx][yy]=='L') dfs(xx,yy);
            if(mp[xx][yy]=='C') mp[xx][yy]='W';
        }
    }
}


int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>mp[i]+1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(!vis[i][j]&&mp[i][j]=='L')
                dfs(i,j),ans++;
        }
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            if (mp[i][j]=='C')
                xy[i][j]=++cnt;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(mp[i][j]=='C'&&(i+j)%2==1) ///防止多次建边
            {
                for(int k=0;k<4;k++)
                {
                    int xx=i+dx[k];
                    int yy=j+dy[k];
                    if(ok(xx,yy)&&mp[xx][yy]=='C')
                        add(xy[i][j],xy[xx][yy]),add(xy[xx][yy],xy[i][j]);
                }
            }
        }
    }
    int sum=0;
    for(int i=1;i<=cnt;i++)
    {
        memset(used,0,sizeof(used));
        if(found(i)) sum++;
    }
    ans+=cnt-sum/2;
    cout<<ans<<endl;
    return 0;
}

H - Paint     solve czh&ym

题意

给你20w条线段,问你选其中若干条两两不相互覆盖的线段,最多能覆盖1-n这个区间中多少个点。 

分析

ym:dp[i]:以第i个为结尾的最多可以覆盖的点,转移O(n),时间复杂度O(n^2),显然不可取,优化:dp[i]以第i个结尾最多可以覆盖的点,显然若按照每个线段尾端排序,二分第一个小于当前起点的区间,那么问题转化为,如何求每个区间前面的所有区间可以覆盖的最大区间,显然这个可以再单独开一个num,num[i]: 1~i区间的最大覆盖(每次从前往后考虑,不要一下天马行空,妄加推断)

czh:二分太好用了,将o(n)转化为lg(n),本来都要放弃的题,起死回生😀

#include<bits/stdc++.h>
using namespace std;
long long num[200010],n,ans;
long long dp[200010];
struct node1
{
    long long s,o;
    long long ma;
}node[200010];
bool cmp(node1 a,node1 b)
{
    return a.o<b.o;
}
int fin(int s,int o,long long m)
{
    int mid=(s+o)/2+1;
    if(s==o)return s;
    if(node[mid].o<m)
    {
        return fin(mid,o,m);
    }
    else
    {
        return fin(s,mid-1,m);
    }
}
int main()
{
    int k;
    scanf("%lld %lld",&n,&k);
    ans=n;
    for(int i=1;i<=k;i++)   scanf("%lld %lld",&node[i].s,&node[i].o);
    sort(node+1,node+1+k,cmp);
    long long answer=0;
    for(int i=1;i<=k;i++)
    {
        num[i]=node[i].o-node[i].s+1;
        if(node[i].s>node[1].o)
        {
            long long maxx=num[fin(1,i-1,node[i].s)];
            dp[i]=maxx+num[i];
            num[i]=max(num[i-1],dp[i]);
        }
        else
        {
            dp[i]=num[i];
            num[i]=max(num[i-1],dp[i]);
        }
        answer=max(answer,dp[i]);
    }
    printf("%lld\n", n-answer);
    return 0;
}

I - Postman    solved by  ym

题意

有一个邮差员要去n家送信,他每次只能带k封信。每一家的坐标为xi,需要送mi封信,然后邮局在0点,问你最少走多少路能送完信。n1e3k,xi,mi1e7

分析

ym:排序后贪心的送即可


J - Shopping   solve by czh&ym

题意

给了n件商品(n2e5n≤2e5),接下来有q个客户(q2e5q≤2e5),每个客户有一个金钱value(value1e18value≤1e18),和购买区间。它会从左到右挨个购买商品,一直尽可能的买每个商品,问最后剩下多少钱

分析

czh:

如果对于每个人都遍历一遍的话,最坏的情况超过了1e9。

由于是取模运算,可以找到这个数列的第一个比他小的数,模它,比它大则无视。一个数最多模lgn下。

接下来最重要的是找到第一个比V小的数的位置

ST表:用倍增的方法存储一段数的最小值,然后再以lgn的速度求出从a开始,比v小的第一个数

ym:由于一个数进行mod操作会减少至少会一半以上,所以最多会操作log次,ST表查找比当前值小的第一个值,查找区间第一个比V小的数的位置,二分常数大

#include <bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
#define LL long long
LL num[maxn];
LL Min[maxn][25];//Min[i][j]从第i个元素到i+2ej-1个元素的最小值
int fin(LL V,int s)
{
    for(int i=20;i>=1;i--)
    {
        while(Min[s][i]<=V&&Min[s][i-1]>V)
        {
            s+=(1<<(i-1));
        }
    }
    if(num[s]<=V)
       return s;
    else
       return maxn;
}
int main()
{
    int n,q,s,o;
    LL V;
    cin>>n>>q;
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&num[i]);
        Min[i][0]=num[i];
    }
    for(int j=1;j<=20;j++)//建立倍增数组
    for(int i=1;i<=n;i++)
    {
        if(i+(1<<(j-1))<=n)
            Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
        else
            Min[i][j]=Min[i][j-1];
    }
    for(int i=1;i<=q;i++)
    {
        scanf("%I64d %d %d",&V,&s,&o);
        int k=fin(V,s);
        while(k<=o)
        {
            V%=num[k];
            k=fin(V,k+1);
        }
        printf("%I64d\n",V);
    }
    return 0;
}

K       solved by ym

 

题意

给出k,和你的排名rank, 2^k个人进行比赛,进行淘汰制,每个人都有一个排名,已知两个人比赛总是排名高的人赢,问你赢的局数数量的期望值

分析

 ym:留下了不会数学的眼泪==

         首先设赢了i局,那么和他分到一组的2^i-1个人都比他菜,那么问题可以化简为从n个人中,已知有k个人比你菜,先要选出x个人,问这x个人都比你菜的概率

         即为C(k,x)/C(n,x),问题得到解决,

         由于结果过大,取log计算概率


L

Different


Summary

Ym:学弟好帅啊,帅气过三题,ym还在犯迷糊= =,该反思一下了,ym要背好自己的锅,努力提高思维啊

Czh:

posted @ 2018-04-15 19:19  Deadlined  阅读(320)  评论(0编辑  收藏  举报