JOISC2020题解

这篇博客主要解析了JOISC2020竞赛的多个题目,涉及动态规划、图论、数据结构和算法等多个方面。D1T1通过状态压缩优化暴力解法,D2T2维护极大团求解,D3T3利用笛卡尔树解决复杂问题,D4T3考虑病人的治疗方案。博主分享了详细的思路和部分代码。
摘要由CSDN通过智能技术生成

等到晚上应该可以更新当日题解。

D1T1

设DP状态 f [ i ] [ 0 / 1 ] [ j ] f[i][0/1][j] f[i][0/1][j]表示前 i i i个数字,总共有 j j j个选择了A,上一个选择了A/B是否合法,直接暴力复杂度 O ( n 2 ) O(n^2) O(n2)

观察性质,可以证明对于每个 i i i,其合法的肯定为一个连续段。证明略。

因此转移只需要记录左右端点。复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define uint unsigned
#define pii pair<int,int>
#define pll pair<ll,ll>
#define IT iterator
#define PB push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);i++)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);i--)
#define CLR(a,v) memset(a,v,sizeof(a));
#define CPY(a,b) memcpy(a,b,sizeof(a));
#define debug puts("wzpakking")
#define y1 ysghysgsygsh
using namespace std;
const int INF=1<<30;
const int N=1000005;
int n,a[N],b[N];
pii f[N][2];
char ans[N];
void upd(pii &x,pii y){
   
	x.fi=min(x.fi,y.fi);
	x.se=max(x.se,y.se);
}
int main(){
   
	scanf("%d",&n);
	For(i,1,2*n) scanf("%d",&a[i]);
	For(i,1,2*n) scanf("%d",&b[i]); 
	f[0][0]=f[0][1]=pii(0,0);
	For(i,1,2*n){
   
		f[i][0]=f[i][1]=pii(INF,-INF);
		if (a[i-1]<=a[i]) upd(f[i][0],f[i-1][0]);
		if (b[i-1]<=a[i]) upd(f[i][0],f[i-1][1]);
		if (a[i-1]<=b[i]) upd(f[i][1],f[i-1][0]);
		if (b[i-1]<=b[i]) upd(f[i][1],f[i-1][1]);
		++f[i][1].fi; ++f[i][1].se;
	}
	int fl=-1,rem=n;
	For(i,0,1)
		if (f[2*n][i].fi<=n&&n<=f[2*n][i].se)
			fl=i;
	if (fl==-1)
		return puts("-1"),0;
	Rep(i,2*n,1){
   
		ans[i]=(fl?'B':'A');
		rem-=fl;
		int v=(fl?b[i]:a[i]);
		if (a[i-1]<=v&&f[i-1][0].fi<=rem&&rem<=f[i-1][0].se) fl=0;
		else assert(b[i-1]<=v&&f[i-1][1].fi<=rem&&rem<=f[i-1][1].se),fl=1;
	}
	printf("%s\n",ans+1);
}

D1T2

设所有正方形的右边界最小值为 m n x mnx mnx

则可以证明若有解一定存在一个竹签使得其横坐标为 m n x mnx mnx。即同可以调整坐标得到一个不劣的方案。

类似的我们求出正方形的左边界最大值,上边界最小值,下边界最大值。类似的也可以证明若有解一定存在一个竹签使得其在直线上。

对于 K ≤ 3 K \leq 3 K3的情况,根据鸽巢原理在解一定会选择两条直线的交点,因此直接爆搜即可,时间复杂度 O ( n × 4 K ) O(n \times 4^K) O(n×4K)

对于 K = 4 K=4 K=4的情况,若一个矩形和三条限制直线相交,此时我们可以忽略这个矩形,因为任意解均会覆盖到这个矩形。

剩下最多只有两条边被覆盖,考虑2-sat模型,表示每个矩形被哪一条边覆盖。限制条件为若两个区间不存在交,则一定不能同时被这一条边覆盖。

利用数据结构或排序来优化2-sat建边即可。

时间复杂度 O ( n ( log ⁡ n + 4 K ) ) O(n(\log n+4^K)) O(n(logn+4K))

代码在路上。

UPD:代码来了!

如果这份代码有bug请联系博主(

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define uint unsigned
#define pii pair<int,int>
#define pll pair<ll,ll>
#define IT iterator
#define PB push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);i++)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);i--)
#define CLR(a,v) memset(a,v,sizeof(a));
#define CPY(a,b) memcpy(a,b,sizeof(a));
#define debug puts("wzpakking")
#define y1 ysghysgsygsh
using namespace std;
const int N=200005;
struct rect{
   
	int x1,y1,x2,y2;
	void init(){
   
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
	}
}a[N];
pii ans[10];
int n,K,cnt[N];
void axiba(int x,int y,int v){
   
	For(i,1,n)
		if (a[i].x1<=x&&x<=a[i].x2)
			if (a[i].y1<=y&&y<=a[i].y2)
				cnt[i]+=v;
}
void dfs(int d){
   
	int mnx=1e9,mxx=1;
	int mny=1e9,mxy=1;
	For(i,1,n) if (!cnt[i]){
   
		mnx=min(mnx,a[i].x2);
		mxx=max(mxx,a[i].x1);
		mny=min(mny,a[i].y2);
		mxy=max(mxy,a[i].y1);
	}
	if (d==1){
   
		if (mxx<=mnx&&mxy<=mny){
   
			ans[1]=pii(mnx,mny);
			For(i,1,K) printf("%d %d\n",ans[i].fi,ans[i].se);
			exit(0); 
		}
		return;
	} 
	For(fx,0,1) For(fy,0,1){
   
		int px=(fx?mxx:mnx);
		int py=(fy?mny:mxy);
		axiba(px,py,1);
		ans[d]=pii(px,py);
		dfs(d-1);
		axiba(px,py,-1);
	}
}
struct node{
   
	int x,v0,v1;
	bool operator <(const node &a)const{
   
		return x<a.x;
	}
};
namespace solver2{
   
	const int M=N*20;
	vector<int> e[M];
	int dfn[M],low[M],nd;
	int vis[M],stk[M],be[M];
	void tarjan(int x){
   
		dfn[x]=low[x]=++*dfn;
		stk[++*stk]=x; vis[x]=1;
		for (auto i:e[x])
			if (!dfn[i]){
   
				tarjan(i);
				low[x]=min(low[x],low[i]);
			}
			else if (vis[i])
				low[x]=min(low[x],dfn[i]);
		if (low[x]==dfn[x]){
   
			++*be;
			int y=19260817;
			for (;y!=x;){
   
				y=stk[(*stk)--];
				vis[y]=0; be[y]=*be;
			}
		}
	}
	void add(int x,int y){
   
		//cout<<x<<' '<<y<<endl;
		if (!x||!y) return;
		//cout<<x<<' '<<y<<endl;
		e[x].PB(y);
	}
	int pr[N],su[N];
	int id[N][2],at[N][2];
	vector<node> vl[10],vr[10];
	void build(int id){
   
		if (!vl[id].size()) return;
		int sz=vl[id].size(),pos=0;
		sort(vl[id].begin(),vl[id].end());
		sort(vr[id].begin(),vr[id].end());
		pr[0]=su[sz+1]=0;
		For(i,1,sz){
   
			pr[i]=++nd;
			add(pr[i-1],pr[i]);
			add(vr[id][i-1].v0,pr[i]);
		}
		Rep(i,sz,1){
   
			su[i]=++nd;
			add(su[i],su[i+1]);
			add(su[i],vl[id][i-1].v1);
		}
		pos=0;
		For(i,0,sz-1){
   
			for (;pos!=sz&&vr[id][i].x>=vl[id][pos].x;++pos);
			add(pr[i+1],su[pos+1]);
		}
		//partA
		Rep(i,sz,1){
   
			su[i]=++nd;
			add(su[i+1],su[i]);
			add(vl[id][i-1].v0,su[i]);
		}
		For(i,1,sz){
   
			pr[i]=++nd;
			add(pr[i],pr[i-1]);
			add(pr[i],vr[id][i-1].v1);
		}
		pos=0;
		For(i,0,sz-1){
   
			for (;pos!=sz&&vr[id][i].x>=vl[id][pos].x;++pos);
			add(su[pos+1],pr[i+1]);
		}
	}
	void solve(){
   
		int mnx=1e9,mxx=1;
		int mny=1e9,mxy=1;
		For(i,1,n){
   
			mnx=min(mnx,a[i].x2);
			mxx=max(mxx,a[i].x1);
			mny=min(mny,a[i].y2);
			mxy=max(mxy,a[i].y1);
		}
		if (mnx>mxx) swap(mnx,mxx);
		if (mny>mxy) swap(mny,mxy);
		For(i,1,n){
   
			a[i].x1=max(a[i].x1,mnx);
			a[i].x2=min(a[i].x2,mxx);
			a[i].y1=max(a[i].y1,mny);
			a[i].y2=min(a[i].y2,mxy);
		}
		//cout<<mnx<<' '<<mny<<' '<<mxx<<' '<<mxy<<endl;
		For(i,1,n){
   
			int fl=0,s=0;
			if (a[i].x1<=mnx&&mnx<=a[i].x2) fl|=1,++s;
			if (a[i].x1<=mxx&&mxx<=a[i].x2) fl|=2,++s;
			if (a[i].y1<=mny&&mny<=a[i].y2) fl|=4,++s;
			if (a[i].y1<=mxy&&mxy<=a[i].y2) fl|=8,++s;
			//cout<<fl<<' '<<s<<endl;
			if (s>=3) continue;
			if (s==0) return;
			id[i][0]=++nd; id[i][1]=++nd;
			if (s==1) add(id[i][1],id[i][0]);
			For(j,0,3) if (fl&(1<<j)){
   
				int flg=(s==1||(1<<j)<(fl^(1<<j))?0:1);
				vl[j].PB((node){
   j&2?a[i].x1:a[i].y1,id[i][flg],id[i][flg^1]});
				vr[j].PB((node){
   j&2?a[i].x2:a[i].y2,id[i][flg],id[i][flg^1]});
				at[i][flg]=j;
			}
		}
		For(i,0,3) build(i);
		For(i,1,nd) if (!dfn[i]) tarjan(i);
		For(i,1,n){
   
			//cout<<id[i][0]<<' '<<id[i][1]<<endl;
			//cout<<be[id[i][0]]<<' '<<be[id[i][1]]<<endl;
			if (id[i][0]&&be[id[i][0]]==be[id[i][1]]) return;
		}
		static int mn[10],mx[10];
		For(i,0,3){
   
			mn[i]=(i&2?mnx:mny);
			mx[i]=(i&2?mxx:mxy);
		}
		For(i,1,n) if (id[i][0]){
   
			int flg=(be[id[i][0]]>be[id[i][1]]);
			int idd=at[i][flg];
			mn[idd]=max(mn[idd],(idd&2?a[i].x1:a[i].y1));
			mx[idd]=max(mx[idd],(idd&2?a[i].x2:a[i].y2));
		}
		printf("%d %d\n",mnx,mn[0]);
		printf("%d %d\n",mxx,mn[1]);
		printf("%d %d\n",mn[2],mny);
		printf("%d %d\n",mn[3],mxy);
		exit(0);
	}
}
void dfs2(int d){
   
	int mnx=1e9;
	For(i,1,n) if (!cnt[i]
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值