原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round2.html
先放榜
再讲故事
时隔一年,我再次亲眼看见了阿鲁巴行为。上一次应该是 cqz 被阿掉。大概是去年的这个时候。
中午提前吃饭,第一体验到胖子烧饼的全瘦肉味道。感觉比绍兴的阿中烧饼难吃。
12:00开始,由于笔记本充电,开场前 20 分钟我只能看题目。
A 秒掉了,xza 几秒钟后也秒掉了,他去写了。
D 题 foreverpiano 秒掉了。
45 分钟,我停止充电,开始切题模式。轮到我写题喽。
我去看 I 题。想了一会儿 23 行干掉了。一个傻逼错误 wa 了一次。
听说 H 题挺可做。我和 piano 肛 H 。我表示只会一个非常恶心的树形dp 。
然后弃疗了。期间 xza 卡一波常数干掉了 G 题。
J 题貌似挺可做。开始写之后我发现搞错了。
在又犹豫了半个小时之后,我开始写树套树。写了不到半个小时,又卡了点常数,交一发,1A !用时 3.8s (lim: 4s)
此时已经 3 个小时了。
xza 开始肝 H 题了。
piano 说 B 题过的人多。于是我一起来看 B 题。这才发现我一开始读错题了。
基环树森林上面跑树形dp 。好像挺不是很难。
感觉时间不够了,于是我开始写啊写。(xza 已经进入调试代码阶段)
写了半个小时,写完了。过样例,交一发 wa 掉。
piano 连续出了两个数据干掉了我。(我的代码又爆了两次罚时)
4 小时 19 分, xza 干掉 H 题。
28 分,piano 再出一个数据干掉了我,我补完最后一个傻逼错误之后 A 掉了(总共三个傻逼错误)。 4 小时 31 分。然后弃疗了。
(为啥没有奖金啊)
最后讲(放)题(代)解(码)
我做了 3 题
I 题
分四个角落,边的中点,其他地方,三个部分直接随便弄一弄就可以了。
#include <bits/stdc++.h> using namespace std; const int N=100005; int n,m,Row[N],Col[N]; int main(){ scanf("%d%d",&n,&m); memset(Row,0,sizeof Row); memset(Col,0,sizeof Col); while (m--){ int x,y; scanf("%d%d",&x,&y); Row[x]=Col[y]=1; } int ans=4-Col[1]-Col[n]-Row[1]-Row[n]; if (n&1){ int x=(n+1)/2; ans+=Col[x]&&Row[x]?0:1; } for (int i=2;i<=n/2;i++) ans+=4-(Col[i]+Col[n-i+1]+Row[i]+Row[n-i+1]); printf("%d",ans); return 0; }
J 题
注意一下如果一个点被打了两种不同的标记,就直接死掉了。
直接树套树实现二维区间修改,单点询问,标记永久化。稍微注意一下常数即可。
%:pragma GCC optimize("Ofast") %:pragma GCC optimize("inline") #include <bits/stdc++.h> #define y1 _0001 using namespace std; const int N=1000005; int n,m,Test; int a[N]; int read(){ char ch=getchar(); int x=0; while (!isdigit(ch)) ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } int T; struct Segment{ int lc[N*10],rc[N*10],tag[N*10],tot; void init(){ memset(lc,0,sizeof lc); memset(rc,0,sizeof rc); memset(tag,0,sizeof tag); tot=0; } void update(int &rt,int L,int R,int xL,int xR){ if (!rt) rt=++tot; if (xL==L&&R==xR){ if (!tag[rt]) tag[rt]=T; else if (tag[rt]!=T) tag[rt]=-1; return; } int mid=(L+R)>>1; if (xR<=mid) update(lc[rt],L,mid,xL,xR); else if (xL>mid) update(rc[rt],mid+1,R,xL,xR); else { update(lc[rt],L,mid,xL,mid); update(rc[rt],mid+1,R,mid+1,xR); } } int query(int rt,int L,int R,int x){ if (!rt) return 0; if (L==R) return tag[rt]; int mid=(L+R)>>1,now; if (x<=mid) now=query(lc[rt],L,mid,x); else now=query(rc[rt],mid+1,R,x); if (now==tag[rt]) return now; if ((now&&tag[rt])||!~now||!~tag[rt]) return -1; return now?now:tag[rt]; } }S; int root[N<<2]; void update(int rt,int L,int R,int xL,int xR,int yL,int yR){ if (xL==L&&R==xR){ S.update(root[rt],1,m,yL,yR); return; } int mid=(L+R)>>1; if (xR<=mid) update(rt<<1,L,mid,xL,xR,yL,yR); else if (xL>mid) update(rt<<1|1,mid+1,R,xL,xR,yL,yR); else { update(rt<<1,L,mid,xL,mid,yL,yR); update(rt<<1|1,mid+1,R,mid+1,xR,yL,yR); } } int query(int rt,int L,int R,int x,int y){ int tag=S.query(root[rt],1,m,y); if (!rt) return 0; if (L==R) return tag; int mid=(L+R)>>1,now; if (x<=mid) now=query(rt<<1,L,mid,x,y); else now=query(rt<<1|1,mid+1,R,x,y); if (now==tag) return now; if ((now&&tag)||!~now||!~tag) return -1; return now?now:tag; } int main(){ n=read(),m=read(),Test=read(); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) a[(i-1)*m+j]=read(); S.init(); while (Test--){ int x1=read(),y1=read(),x2=read(),y2=read(); T=read(); update(1,1,n,x1,x2,y1,y2); } int ans=0; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++){ int now=query(1,1,n,i,j); if (now!=0&&now!=a[(i-1)*m+j]) ans++; } printf("%d\n",ans); return 0; }
B 题
显然是基环树森林上面树形dp裸题。
对于树的情况,直接简单树形dp。对于环上面的,我们选一条边分别让他连或者不连达到断环的目的,然后线性dp一下即可。注意一下二元环的情况(然而我并不用判)(一元环我判了,不知道数据里面有没有)
#include <bits/stdc++.h> typedef long long LL; using namespace std; const int N=100005; struct Gragh{ static const int M=N*2; int cnt,y[M],nxt[M],fst[N]; void clear(){ cnt=0; memset(fst,0,sizeof fst); } void add(int a,int b){ y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt; } }g; int n,vis[N],father[N],rvis[N]; int id[N],tot=0; LL p[N],d[N],dp[N][2]; int flag[N*2]; void DP(int x){ vis[x]=1; dp[x][0]=0; for (int i=g.fst[x];i;i=g.nxt[i]){ int y=g.y[i]; if (vis[y]) continue; DP(y); flag[i]=1; dp[x][0]+=dp[y][1]; } dp[x][1]=dp[x][0]+p[x]-d[x]; for (int i=g.fst[x];i;i=g.nxt[i]){ int y=g.y[i]; if (!flag[i]) continue; dp[x][1]=min(dp[x][1],dp[x][0]-dp[y][1]+dp[y][0]+p[y]); } } LL rdp[N]; LL solve(int x){ tot=0; for (int i=x;!rvis[i];i=father[i]) id[++tot]=i,rvis[i]=1; int L=father[id[tot]],R=tot; for (int i=1;i<=tot;i++) if (id[i]==L){ L=i; break; } for (int i=L;i<=R;i++) vis[id[i]]=1; for (int i=L;i<=R;i++) DP(id[i]); LL ans=1LL<<50; rdp[L-1]=0,rdp[L]=dp[id[L]][1]; for (int i=L+1;i<=R;i++) rdp[i]=min(rdp[i-2]+dp[id[i]][0]+dp[id[i-1]][0]+p[id[i-1]],rdp[i-1]+dp[id[i]][1]); ans=min(ans,rdp[R]); rdp[L]=dp[id[R]][0]+dp[id[L]][0]+p[id[R]]; rdp[L+1]=rdp[L]+dp[id[L+1]][1]; for (int i=L+2;i<R;i++) rdp[i]=min(rdp[i-2]+dp[id[i]][0]+dp[id[i-1]][0]+p[id[i-1]],rdp[i-1]+dp[id[i]][1]); ans=min(ans,rdp[R-1]); if (L==R) ans=dp[id[L]][1]; return ans; } int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%lld",&p[i]); for (int i=1;i<=n;i++) scanf("%lld",&d[i]); g.clear(); for (int i=1,j;i<=n;i++){ scanf("%d",&j); g.add(i,j); g.add(j,i); father[i]=j; } memset(vis,0,sizeof vis); memset(rvis,0,sizeof rvis); memset(dp,0,sizeof dp); LL ans=0; for (int i=1;i<=n;i++) if (!vis[i]) ans+=solve(i); printf("%lld",ans); return 0; }