2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1)
Time:2018.4.15 13:00-18:00
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个标记。2≤n≤100000,0≤k≤n,2≤r≤n
分析
单调队列问题
ym:处理好区间[1,r],从左往右的贪心的移动即可,因为移动一个仅能变化一个值,所以对于<=r的数量的标记都可以做
D
推组合数公式较难,组合数问题
E
题意
给定 n个点,已知选定 k个点,再多选一个点,使得 k+1 个点的凸包最大。
分析
F - Illumination solved by ym
题意
给你一个n*n的方格,里面有l个方格有灯,每个灯可以选择左右发射长度r格的光或者上下发射,问你是否有一种方案,使得每个格子,不会被同为纵向(横向)的多束光照射。n≤100
分析
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点,问你最少走多少路能送完信。(n≤1e3),(k,xi,mi≤1e7)
分析
ym:排序后贪心的送即可
J - Shopping solve by czh&ym
题意
给了n件商品(n≤2e5n≤2e5),接下来有q个客户(q≤2e5q≤2e5),每个客户有一个金钱value(value≤1e18value≤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: