ICPC2020 World Final

ICPC2020 WF C

洛谷

题意:给定矩形区域左下角\((0,0)\),右上角\((dx,dy)\),其中\(2<=dx,dy<=10^5\),在矩形区域有\(n(n<=100)\)个点,代表圆塔。游客需要对这些圆塔进行拍照,使得照片上的圆塔按照指定的顺序(从左往右)出现。现在指定一个顺序,假设相机的拍摄角度为180°,求矩形区域内能够拍出符合要求的照片的区域面积。

分析:半平面交模板题。对于两个点\((x_1,y_1),(x_2,y_2)\),构造向量\((x_2-x_1,y_2-y_1)\),此时该向量左边区域都是合法的。加入这些向量以及原来矩形边界的四个向量即可。

半平面交模板题博客

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N = 1e2 + 10;
const int M = 1e4 + 10;
const double EPS = 1e-6;
int dx,dy,n,tot;
int xx[N],yy[N],ord[N];
struct node {
	double x, y;
};
node ppx[M];
node operator - (node a, node b) {
	node t;
	t.x = a.x - b.x;
	t.y = a.y - b.y;
	return t;
}
double operator ^ (node a, node b) { return a.x * b.y - a.y * b.x; }
struct Line {
	node s, e;
};
Line L[M], que[M];
double getAngle(node a) { return atan2(a.y, a.x); }
double getAngle(Line a) { return atan2(a.e.y - a.s.y, a.e.x - a.s.x); }
bool cmp(Line a, Line b) {
	double A = getAngle(a);
	double B = getAngle(b);
	return A < B;
}
node getIntersectPoint(Line a, Line b) {
	double a1 = a.s.y - a.e.y, b1 = a.s.x - a.e.x, c1 = 1.0 * a.s.x * a.e.y - 1.0 * a.e.x * a.s.y;
	double a2 = b.s.y - b.e.y, b2 = b.s.x - b.e.x, c2 = 1.0 * b.s.x * b.e.y - 1.0 * b.e.x * b.s.y;
	node t;
	t.x = (1.0 * c1 * b2 - 1.0 * c2 * b1) / (1.0 * a2 * b1 - 1.0 * a1 * b2);
	t.y = (1.0 * c1 * a2 - 1.0 * c2 * a1) / (1.0 * a2 * b1 - 1.0 * a1 * b2);
	return t;
}
bool onRight(Line a, Line b, Line c) {
	node o = getIntersectPoint(b, c);
	if (((a.e - a.s) ^ (o - a.s)) < 0) return true;
	return false;
}
double HalfPlaneIntersection() {
	sort(L + 1, L + tot + 1, cmp);
	int head = 1, tail = 1;
	que[1] = L[1];
	for (int i = 2; i <= tot; i++) {
		while (head < tail && onRight(L[i], que[tail], que[tail - 1])) tail--;
		while (head < tail && onRight(L[i], que[head], que[head + 1])) head++;
		que[++tail] = L[i];
		if (fabs(getAngle(que[tail]) - getAngle(que[tail - 1])) < EPS) {
			tail--;
			if (((que[tail].e - que[tail].s) ^ (L[i].e - que[tail].s)) > EPS)que[tail] = L[i];
		}
	}
	while (head < tail && onRight(que[head], que[tail], que[tail - 1])) tail--;
	while (head < tail && onRight(que[tail], que[head], que[head + 1])) head++;
	if (tail - head < 2) return 0;
	double ans = 0;
	int tot_jd = 0;
	for (int i = head; i < tail; ++i) {
		ppx[++tot_jd] = getIntersectPoint(que[i], que[i + 1]);
	}
	ppx[++tot_jd] = getIntersectPoint(que[tail], que[head]);
	for (int i = 2; i < tot_jd; ++i) {
		double x1 = ppx[1].x, y1 = ppx[1].y;
		double x2 = ppx[i].x, y2 = ppx[i].y;
		double x3 = ppx[i + 1].x, y3 = ppx[i + 1].y;
		ans = ans + (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
	}
	return ans / 2.0;
}
int main() {
	int dx=read(),dy=read(),n=read();
	L[++tot].s.x=0;L[tot].s.y=0;L[tot].e.x=dx;L[tot].e.y=0;
	L[++tot].s.x=dx;L[tot].s.y=0;L[tot].e.x=dx;L[tot].e.y=dy;
	L[++tot].s.x=dx;L[tot].s.y=dy;L[tot].e.x=0;L[tot].e.y=dy;
	L[++tot].s.x=0;L[tot].s.y=dy;L[tot].e.x=0;L[tot].e.y=0;
	for(int i=1;i<=n;++i){
		xx[i]=read();yy[i]=read();
	}
	for(int i=1;i<=n;++i)ord[i]=read();
	for(int i=1;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			L[++tot].s.x=xx[ord[j]];
			L[tot].s.y=yy[ord[j]];
			L[tot].e.x=xx[ord[i]];
			L[tot].e.y=yy[ord[i]];
		}
	}
	printf("%.6lf\n", HalfPlaneIntersection());
	return 0;
}

ICPC2020 WF D

洛谷

题意:只含A、C、G、T的字符串,如果头(尾)是回文串,则可以从前往后(从后往前)折叠回文部分使得字符串长度缩短,求 若干次操作之后 字符串的最小可能长度。字符串长度\(n(n<=4*10^6)\)

分析:先用manachar求出 以每个字符为中心 的最长回文串的半径\(f[i]\),再求出每个字符分别 向左和向右 最远 可以折叠到哪个位置\(l[i],r[i]\),最后让头指针head,尾指针tail分别向后向前折叠即可,答案就是\(\frac{tail-head}{2}+1\)

\(f[i]\)的manachar算法是模板不多说。考虑求\(l[i]\),首先因为可折叠的回文串一定是关于\(ACGT\)的偶回文,所以在manachar处理之后,回文串正中间一定是我们自己加入的隔板字符\('|'\)。因为\(l[i]=min(2*j-i)\),其中\(j\)表示包含字符\(i\)的回文串的中心,因此\(j\)越小越好,所以如下图所示,从前往后遍历每一个回文串,每次能够更新的\(l[i]\)就是绿色部分。

\(r[i]\)同理,从后往前遍历每一个回文串即可。求出\(l[i],r[i]\)之后就可以让\(head\)往后折叠,\(tail\)往前折叠。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
    char ch=getchar();int x=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} 
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 
	return x*f;
}
const int N=8e6+10;
int cnt,maxr,mid,f[N],l[N],r[N];                         
char ch,s[N];       
int main(){
   	s[++cnt]='~';            
   	s[++cnt]='|';      
   	string s1;
   	cin>>s1;
   	for(int i=0;i<s1.size();++i){
   		s[++cnt]=s1[i];
    	s[++cnt]='|';
	}
   	for(int i=1;i<=cnt;++i){
   		l[i]=i;
		r[i]=i;	
	}
	int tot=0;
   	for(int i=1;i<=cnt;i++){   //manachar模板
	    if(i<maxr) 
	    	f[i]=min(f[(mid<<1)-i],maxr-i); 
		else f[i]=1;                                              
		while(s[i-f[i]]==s[i+f[i]])++f[i];			         
		if(f[i]+i>maxr)maxr=f[i]+i,mid=i;        
   	}
   	int now=0;
//第4个字符是第二个隔板字符,第一个隔板字符肯定不存在以其为中心的回文串
   	for(int i=4;i<=cnt;i+=2){
   		for(int j=max(now+1,i+1);j<=i+f[i]-1;++j){
   			l[j]=2*i-j;
		}
		now=i+f[i]-1;
	}
	now=cnt;
//从最后一个隔板字符开始往前遍历
	for(int i=cnt;i>=2;i-=2){
   		for(int j=i-f[i]+1;j<=min(i-1,now-1);++j){
   			r[j]=2*i-j;
		}
		now=i-f[i]+1;
	}
//第一个ACGT字符的下标为3,最后一个的下标为cnt-1
	int head=3,tail=cnt-1;    
	while(r[head]!=head)
		head=(head+r[head])/2+1;
	while(l[tail]!=tail&&l[tail]>=head)//一定别忘了 l[tail]>=head
		tail=(tail+l[tail])/2-1;
	cout<<(tail-head)/2+1<<endl;      
   	return 0;
}

ICPC2020 WF E

洛谷

题意:对于区间\([1,n](n<=2*10^5)\),初始全部为0,进行\(k(k<=2*10^5)\)次操作,操作分为以下四种:

(1)'R l r':区间\([l,r]\)加1

(2)'D l r':区间\([l,r]\)减1

(3)'H l r':区间\([l,r]\)形成山丘,即下标\(l,r\)分别加1,下标\(l+1,r-1\)分别加2,下标\(l+2,r-2\)分别加3,以此类推......

(4)'V l r':区间\([l,r]\)形成山谷,即下标\(l,r\)分别减1,下标\(l+1,r-1\)分别减2,下标\(l+2,r-2\)分别减3,以此类推......

最后完整输出区间\([1,n]\)

分析:四类操作都是对区间进行加加减减的操作;考虑差分数组如下:

R D H V
原数组a \([l,r]+1\) \([l,r]-1\)
a的差分数组b \(b[l]+1\)
\(b[r+1]-1\)
加减与左相反 \([l,mid]+1\)
\([mid+1(+1),r+1]-1\)
加减与左相反
b的差分数组c \(c[l]+1,c[l+1]-1\)
\(c[r+1]-1,c[r+2]+1\)
加减与左相反 \(c[l]+1,c[mid+1]-1\)
\(c[mid+1(+1)]-1,c[r+2]+1\)
加减与左相反

对于操作(1)(2)第一次差分之后,就已经将区间加减转换成了单点修改;那为什么要两次差分?是因为操作(3)(4) 对第一次差分数组b 是前一段+x,后一段-x,本质上仍是区间加减,因此再一次差分,才能将所有操作转换为单点修改。

将差分数组转换为原数组就是进行一次前缀和。综上,整个算法的时间复杂度是\(O(n)\)。如果在进行第一次差分之后,再用线段树来进行区间加减来维护差分数组\(b\),则时间复杂度就是\(O(nlogn)\)

最后再提醒一句,十年Oi一场空......

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
    char ch=getchar();int x=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} 
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 
	return x*f;
}
const int N=2e5+5;
ll a[N],b[N],c[N];
int main() {
    int n=read(),k=read();
    for(int i=1;i<=k;++i){
    	char op;cin>>op;
    	int l=read(),r=read();
    	if(op=='R'){
    		++c[l];--c[l+1];
    		--c[r+1];++c[r+2];
		}
		else if(op=='D'){
			--c[l];++c[l+1];
    		++c[r+1];--c[r+2];
		}
		else if(op=='H'){
			int mid=(l+r)/2;
			if((r-l)%2==0){
				++c[l];--c[mid+1];
				--c[mid+1];++c[r+2];
			}
			else{
				++c[l];--c[mid+1];
				--c[mid+2];++c[r+2];	
			}
		}
		else if(op=='V'){
			int mid=(l+r)/2;
			if((r-l)%2==0){
				--c[l];++c[mid+1];
				++c[mid+1];--c[r+2];
			}
			else{
				--c[l];++c[mid+1];
				++c[mid+2];--c[r+2];
			}
		}
	}
	for(int i=1;i<=n;++i){
		b[i]=b[i-1]+c[i];
		a[i]=a[i-1]+b[i];
		cout<<a[i]<<endl;
	}
	return 0;
}

ICPC2020 WF G

洛谷

题意:给定\(n(n<=2*10^5)\)个三元组\((x_i,y_i,z_i)\),对于每一个三元组\((x,y,z)\),定义其机会成本为$$\max\limits_{1\leqslant i\leqslant n}(\max(x_i-x,0)+\max(y_i-y,0)+\max(z_i-z,0))$$ 求 机会成本最小 的三元组,输出该最小值及三元组的编号。

分析:观察机会成本的表达式,发现只有差值为正才会产生贡献,也就是对于\(x,y,z\)三个指标,每一个都有产生贡献和不产生贡献两种情况,一共有\(2^3=8\)种情况:

$$\max\limits_{1\leqslant i\leqslant n}(0,x_i-x,y_i-y,z_i-z,x_i+y_i-x-y,x_i+z_i-x-z,y_i+z_i-y-z,x_i+y_i+z_i-x-y-z)$$

\(1\leqslant i\leqslant n\)放进去,就转换成了,分别找到最大的\(x_i,y_i,z_i,x_i+y_i,x_i+z_i,y_i+z_i,x_i+y_i+z_i\),对于每个三元组,分别用这七个最大值直接作差计算,所有差的最大值就是机会成本。时间复杂度\(O(n)\)

最后提醒一句,十年oi一场空......

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
    char ch=getchar();ll x=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} 
	while('0'<=ch&&ch<='9'){x=1ll*x*10+ch-'0';ch=getchar();} 
	return x*f;
}
const int N=2e5+5;
ll x[N],y[N],z[N],maxn[10];
int main() {
    int n=read();
    for(int i=1;i<=n;++i){
    	x[i]=read();y[i]=read();z[i]=read();
    	maxn[1]=max(maxn[1],x[i]);
    	maxn[2]=max(maxn[2],y[i]);
    	maxn[3]=max(maxn[3],z[i]);
    	maxn[4]=max(maxn[4],x[i]+y[i]);
    	maxn[5]=max(maxn[5],x[i]+z[i]);
    	maxn[6]=max(maxn[6],y[i]+z[i]);
    	maxn[7]=max(maxn[7],x[i]+y[i]+z[i]);
	}
	int ans1=2147483647,ans2=1;
	for(int i=1;i<=n;++i){
		ll ret=0;
		ret=max(ret,maxn[1]-x[i]);
		ret=max(ret,maxn[2]-y[i]);
		ret=max(ret,maxn[3]-z[i]);
		ret=max(ret,maxn[4]-x[i]-y[i]);
		ret=max(ret,maxn[5]-x[i]-z[i]);
		ret=max(ret,maxn[6]-y[i]-z[i]);
		ret=max(ret,maxn[7]-x[i]-y[i]-z[i]);
		if(ret<ans1){
			ans1=ret;ans2=i;
		}
	}
	cout<<ans1<<" "<<ans2<<endl;
	return 0;
}

ICPC2020 WF J

洛谷

题意:给定一颗树,选择两条路径,使得以这两条路径为主轴遍历整棵树时的距离最小,两条路径可以有公共边,求这个最小的距离。树的节点n<=100000.

分析:设每一条树上边的距离为c,通俗点讲,不在这两条路径上的边产生的贡献是2c,在这两条路径上的边产生的贡献是c,要使贡献最小化。总存在一种最优方案,使得两条路径没有公共边。因为如果一条边被选了两次,其产生的贡献 和 这条边不被选 是一样的,也就是说如果有一个方案包含公共边,则它会有一种 不选这条公共边的 等效方案。不选任何路径的时候,遍历整棵树,每条边的贡献都是2c,那么选出的两条路径长度和val肯定是越大越好,直接 $ (\sum2c)-val$就是答案。于是整道题转化成了从树上选两条没有公共边的路径 且 路径距离和最大。

这两条路径,如果其中一条路径就是树的直径,那么另一条路径就是树的直径上 某一个节点,以它为根节点,其不包含直径部分的子树 构成的新树 的直径。另一种情况,如果两条路径都不是树的直径,那么这两条路径必定分别包含树的直径的某一部分,即一定是直径的最左端、最右端 和 以这部分端点为根节点,其不包含直径部分的子树内 以根节点为起始点的最长链构成。值得说明的是,这种情况下,选出的最左端和最右端的端点一定不会重合,因为这样就是第一种情况了。

设ans为选出的两条路径距离之和。综上所述,先两次dfs求出树的直径maxn,同时记录直径上的点,然后dfs遍历(树形dp)直径上的点,求出以其为根,其不包含直径部分的子树内的直径ret 以及 包含该根节点的最长链,每一次更新\(ans=max(ans,maxn+ret)\),即讨论第一种情况。对于第二种情况,设\(L[i]\)\(R[i]\)分别表示直径上第i个节点 其左端和右端两部分中(均不包括当前节点i) 直径的一部分加最长链 的最大值,所以最后比较\(ans=max(ans,L[i]+R[i-1])\)

本题相当于综合了树上求直径的两种方法,两次dfs/bfs 或者 树形dp。时间复杂度\(O(n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
    char ch=getchar();int x=0,f=1;
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} 
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 
	return x*f;
}
const int N=1e5+5;
int n,ans,ret,cnt,ans_tot,q[N],dis[N],dist[N],pre[N],pre_bj[N],L[N],R[N];
int tot,head[N],nxt[N<<1],to[N<<1],w[N<<1];
void add(int a,int b,int c){
	nxt[++tot]=head[a];head[a]=tot;to[tot]=b;w[tot]=c;
}
inline void dfs1(int u,int fa){
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];if(v==fa)continue;
		dis[v]=dis[u]+w[i];dfs1(v,u);
	}
}
inline void dfs2(int u,int fa){
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];if(v==fa)continue;
		dis[v]=dis[u]+w[i];pre[v]=u;dfs2(v,u);
	}
}
inline void dfs(int u,int fa){
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];if(v==fa)continue;
		if(pre_bj[u]==v||pre_bj[v]==u)continue;
		dfs(v,u);
		ret=max(ret,dist[u]+dist[v]+w[i]);
		dist[u]=max(dist[u],dist[v]+w[i]);
	}
}
int main() {
    n=read();
    for(int i=1;i<n;++i){
    	int a=read(),b=read(),c=read();
    	add(a,b,c);add(b,a,c);ans_tot+=c;
	}
	dfs1(1,0);//第一次求直径的预处理,任意一点为根
	int rt1,rt2,rt3,rt4;
	int maxn=0,pos1,pos2;
	for(int i=1;i<=n;++i)if(dis[i]>maxn)maxn=dis[i],pos1=i;//找到距离最远的点pos1
	memset(dis,0,sizeof(dis));dfs2(pos1,0);//以距离最远的点pos1为根再次dfs
	maxn=0;for(int i=1;i<=n;++i)if(dis[i]>maxn)maxn=dis[i],pos2=i;
	rt1=pos1;rt2=pos2;
	while(pos2!=pos1){
		pre_bj[pos2]=pre[pos2];q[++cnt]=pos2;
		ret=0;dfs(pos2,0);ans=max(ans,maxn+ret);
		pos2=pre[pos2];
	}
	q[++cnt]=pos1;
	int tmp = 0;
	for(int i=2;i<=cnt;++i){
		tmp=max(tmp,maxn-dis[q[i-1]]+dist[q[i-1]]);
		L[i]=tmp;	
	}
	tmp = 0;
	for(int i = cnt-1; i >= 1; --i){
		tmp=max(tmp,dis[q[i+1]]+dist[q[i+1]]);
		R[i]=tmp;	
	}
	for(int i=1;i<=cnt; ++i)
		ans=max(ans, L[i]+R[i-1]);
	cout<<ans_tot*2-ans<<endl;
	return 0;
}

posted on 2022-10-11 21:55  PPXppx  阅读(161)  评论(0编辑  收藏  举报