比赛链接
赛时
难绷
先通读四道题,T2有一个反悔贪心的思路,T3一眼差分,T1应该不会太难,心态良好。
于是直接开T1。思考近45min后想到直接硬拆绝对值,开始码。结果由于自己前期思路不严谨和代码实现问题,硬调2.5h才过大样例,直接裂开。
T2做法假了,T3也只能想到差分。
赛后出分发现第一题挂到\(10pts\),裂开\(*2\)。检查后发现是快读打错了,裂开\(*3\)。
Sol
T1王哥与荷塘
先找一下逆时针旋转\(90^{\circ}\)规律,手玩一下发现是由\((x,y)\)变为\((-y,x)\)。
再考虑套路拆绝对值,发现无非就是\(x_i+y_i-x_j-y_j\)和\(x_i-y_i-x_j+y_j\)两种情况(其他两种将\(i,j\)互换即可转换)。于是考虑维护\(x_i+y_i\) , \(x_j-y_j\) , \(x_i-y_i\) , \(-x_j+y_j\)。不带修就是个线段树板题,由于要考虑逆时针转动,于是还要维护 \(x_i+y_i\),\(-y_i+x_i\),\(-x_i-y_i\),\(y_i-x_i\)(目的是维护\(x_i+y_i\) , \(x_j-y_j\)),\(x_i-y_i\),\(y_i+x_i\),\(-x_i+y_i\),\(-y_i-x_i\)(目的是维护\(x_i-y_i\) , \(-x_j+y_j\)),先自己手推一下这两组数各自在逆时针旋转\(90^{\circ}\)互相变化的规律,再用一个懒标记即可。
#include<bits/stdc++.h>
using namespace std;
struct A
{int x,y;
}a[500000];
struct T
{int dis[6][3],lz,l,r;//0 x y//1 x -y//2 -x -y//3 -x y
}t[3500000];
struct Q
{int dis[4][4];
}blanc;
void pushup(int rt)
{for(int i=0;i<4;i++){for(int j=0;j<=1;j++){t[rt].dis[i][j]=max(t[rt<<1].dis[i][j],t[rt<<1|1].dis[i][j]);}}
}
int mx[5000];
void upd(int rt,int x)
{for(int i=0;i<4;i++) mx[i]=t[rt].dis[i][0];for(int i=0;i<4;i++){t[rt].dis[i][0]=mx[(i+x)%4];}for(int i=0;i<4;i++) mx[i]=t[rt].dis[i][1];for(int i=0;i<4;i++){t[rt].dis[i][1]=mx[((i-x)%4+4)%4];}
}
void pushdown(int rt)
{upd(rt<<1,t[rt].lz);upd(rt<<1|1,t[rt].lz);t[rt<<1].lz+=t[rt].lz;t[rt<<1|1].lz+=t[rt].lz;t[rt<<1].lz%=4,t[rt<<1|1].lz%=4;t[rt].lz=0;
}
void build(int rt,int l,int r)
{t[rt].l=l,t[rt].r=r;if(l==r){t[rt].dis[0][0]=a[l].x+a[l].y;t[rt].dis[1][0]=-a[l].y+a[l].x;t[rt].dis[2][0]=-a[l].x-a[l].y;t[rt].dis[3][0]=a[l].y-a[l].x;t[rt].dis[0][1]=a[l].x-a[l].y;t[rt].dis[1][1]=a[l].y+a[l].x;t[rt].dis[2][1]=-a[l].x+a[l].y;t[rt].dis[3][1]=-a[l].y-a[l].x;return ;}int mid=(l+r)>>1;build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);pushup(rt);
}
void update(int rt,int hl,int hr)
{int l=t[rt].l,r=t[rt].r;if(l>hr||r<hl) return ;if(hl<=l&&r<=hr){t[rt].lz++;upd(rt,1);return ;}pushdown(rt);update(rt<<1,hl,hr);update(rt<<1|1,hl,hr);pushup(rt);
}
Q query(int rt,int hl,int hr)
{int l=t[rt].l,r=t[rt].r;if(l>hr||r<hl){return blanc;}if(hl<=l&&r<=hr){Q tmp=blanc;tmp.dis[0][0]=t[rt].dis[0][0];tmp.dis[1][0]=t[rt].dis[2][0];tmp.dis[0][1]=t[rt].dis[0][1];tmp.dis[1][1]=t[rt].dis[2][1];return tmp;}pushdown(rt);Q tmp=blanc,lc=query(rt<<1,hl,hr),rc=query(rt<<1|1,hl,hr);tmp.dis[0][0]=max(lc.dis[0][0],rc.dis[0][0]);tmp.dis[1][0]=max(lc.dis[1][0],rc.dis[1][0]);tmp.dis[0][1]=max(lc.dis[0][1],rc.dis[0][1]);tmp.dis[1][1]=max(lc.dis[1][1],rc.dis[1][1]);return tmp;
}
int read()
{int x=0,z=1;char ch=getchar();while(!('0'<=ch&&ch<='9')){if(ch=='-') z=-1;ch=getchar();}while(('0'<=ch&&ch<='9')){x=(x<<1)+(x<<3) +ch-'0';ch=getchar();}return x*z;
}
int main()
{freopen("fish.in","r",stdin);freopen("fish.out","w",stdout);for(int i=0;i<=2;i++){for(int j=0;j<=2;j++){blanc.dis[i][j]=-300000000;}}int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){a[i].x=read(),a[i].y=read();}build(1,1,n);for(int i=1;i<=m;i++){int op;scanf("%d",&op);if(op==1){int l,r;l=read(),r=read();update(1,l,r);}else{int l,r;l=read(),r=read();Q t=query(1,l,r);printf("%d\n",max(0,max(t.dis[0][0]+t.dis[1][0],t.dis[0][1]+t.dis[1][1])));}}
}
T2 AT_jsc2019_qual_e Card Collector 王哥与演出
图论建模还是太神仙了。
首先思考一下题目给的条件,每行只选一个,接着每列只选一个,由于这种唯一对应的性质,很容易让人想到二分图匹配,但数据范围并不允许我们这样做。
但这给我们一个图论建模的思路。考虑一张卡片在 \((x,y)\) 上,权值为 \(v\),我们可以看作在点 \(x\) 和点 \(y+r\) 之间连了一条权值为 \(v\) 的边。为了区分第一步操作和第二步操作,有一个 trick 是让边有方向。钦定如果是在第一步操作中(即从每一行中选取卡片),让边的方向为 \(x\) 到 \(y+r\),否则让边的方向为 \(y+r\) 到 \(x\)。
接下来研究一下图的性质。根据题目限制我们可以发现一个点最多只能向外连一条边(因为若不保证这一点,则根据钦定的变的定义,会出现一行选取多个点或一列选取多个点的情况),即出度为 \(1\),这显然就是内向基环树森林。
但有向边不好处理,所以接下来一步比较奇妙的处理就是让有向边变成无向边。那么为什么可以直接转换?我们只要保证一个点在有向图中出度为 \(1\) 即可保证方案合法,那么再边变为无向后的新图中只要保证每一个联通块都是树或基环树。
稍微证明一下:如果是树,一种构造方法是让边的方向一致朝下。如果是基环树,只要让环部分的边转一圈回到起始点,其他边向环的方向指即可。如下图所示:


那么唯一的问题在于如何求出选哪些边是的它们组成一颗颗树或者基环树。其实只用在 Kruskal 求最大生成树的过程中加一个标记数组标记该连通块是基环数还是树即可。遵循以下规则:
-
如果联通块内部点相连接,那么该联通块一定是一棵树,连完后它成为一颗基环树。
-
如果是两个联通块相连,那么一定不能是两个基环树,假如两个联通块有一个是基环树,那么连完后整体是基环树,反之是树。
可自行证明一下这样做一定是符合定义的,并不难证。
代码如下:
#include <bits/stdc++.h>
#define int long long
using namespace std;
struct A {int x, y, v;bool operator<(const A &h) const { return v < h.v; }
} e[5000000];
int tmp;
int t[5000000];
bool cmp(A x, A y) { return x.v > y.v; }
int find(int x) {if (t[x] == x) {return x;}return t[x] = find(t[x]);
}
int bz[5000000];
signed main() {int n, r, c;scanf("%lld%lld%lld", &n, &r, &c);for (int i = 1; i <= n; i++) {int x, y, v;scanf("%lld%lld%lld", &x, &y, &v);e[++tmp] = { x, y + r, v };}sort(e + 1, e + 1 + tmp, cmp);for (int i = 1; i <= r + c; i++) {t[i] = i;}int ans = 0;for (int i = 1; i <= tmp; i++) {int fx = find(e[i].x), fy = find(e[i].y);if (fx == fy) {if (!bz[fx]) {ans += e[i].v;bz[fx] = 1;}continue;}if (!bz[fx] || !bz[fy]) {t[fx] = fy;bz[fy] =bz[fx]=bz[fy]|bz[fx];ans += e[i].v;}}printf("%lld", ans);
}
T3
咕
T4
太菜了