JOI 2020 Final 题解

前言

好久没更。来更一篇
竟然直接ak了…

loj#3252. 「JOI 2020 Final」只不过是长的领带

考虑去除后的匹配方式,将两个序列排序

注意到如果存在 i < j i<j i<j满足匹配的位置 c i > c j c_i>c_j ci>cj,由于此时满足 b i < b j , a c i > a c j b_i<b_j,a_{c_i}>a_{c_j} bi<bj,aci>acj,所以将两个交换一定不劣

故可以证明直接排序后 b i b_i bi配对 a i a_i ai是最优的,预处理前缀后缀 m a x max max即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
template<typename T>inline void write(T x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
template<typename T>inline void pr1(T x){write(x);putchar(' ');}
template<typename T>inline void pr2(T x){write(x);putchar('\n');}
const int MAXN=200005;
int a[MAXN],n;pii b[MAXN];
int pre[MAXN],suf[MAXN],ans[MAXN];
int main()
{
	n=read();
	for(int i=1;i<=n+1;i++)b[i]=mp(read(),i);sort(b+1,b+1+n+1);
	for(int i=1;i<=n;i++)a[i]=read();sort(a+1,a+1+n);
	for(int i=1;i<=n;i++)pre[i]=max(pre[i-1],max(b[i].first-a[i],0));
	for(int i=n;i>=1;i--)suf[i]=max(suf[i+1],max(b[i+1].first-a[i],0));
	for(int i=1;i<=n+1;i++)ans[b[i].second]=max(pre[i-1],suf[i]);
	for(int i=1;i<=n+1;i++)pr1(ans[i]);puts("");
	return 0;
}

loj#3253. 「JOI 2020 Final」JJOOII 2

考虑等价于求头尾相距最短的 K K K J O I JOI JOI 串,可以考虑使用几个单调指针右移来获得答案

处理一下每个位置作为第 K K K J J J,第 2 K 2K 2K O O O,第 3 K 3K 3K I I I时前面第一个 J J J最近的位置在哪里

所有操作都不难使用单调指针维护

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
template<typename T>inline void write(T x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
template<typename T>inline void pr1(T x){write(x);putchar(' ');}
template<typename T>inline void pr2(T x){write(x);putchar('\n');}
const int MAXN=200005;
char ch[MAXN];
int n,K,f[MAXN];
int main()
{
	n=read();K=read();scanf("%s",ch+1);
	for(int i=1,cnt=0,j=0;i<=n;i++)if(ch[i]=='J')
	{
		++cnt;if(cnt==1)j=i;
		if(cnt>K){do{++j;}while(ch[j]!='J');--cnt;}
		if(cnt==K)f[i]=j;
	}
	for(int i=1,cnt=0,j=0,mx=0;i<=n;i++)if(ch[i]=='O')
	{
		++cnt;if(cnt==1)while(j!=i)mx=max(mx,f[j++]);
		if(cnt>K)
		{
			do
			{
				++j;
				if(ch[j]=='J')mx=max(mx,f[j]);
			}while(ch[j]!='O');--cnt;
		}if(cnt==K)f[i]=mx;
	}
	for(int i=1,cnt=0,j=0,mx=0;i<=n;i++)if(ch[i]=='I')
	{
		++cnt;
		if(cnt==1)for(;j!=i;++j)if(ch[j]=='O')mx=max(mx,f[j]);
		if(cnt>K)
		{
			do
			{
				++j;
				if(ch[j]=='O')mx=max(mx,f[j]);
			}while(ch[j]!='I');--cnt;
		}if(cnt==K)f[i]=mx;
	}int ans=999999999;
	for(int i=1;i<=n;i++)if(ch[i]=='I'&&f[i]!=0)ans=min(ans,i-f[i]+1-3*K);
	pr2(ans==999999999?-1:ans);
	return 0;
}

loj#3254. 「JOI 2020 Final」集邮比赛 3

拆环为段,考虑做 f l , r , k , 0 / 1 f_{l,r,k,0/1} fl,r,k,0/1表示已经走完了 [ l , r ] [l,r] [l,r]这一段,中间收获了 k k k个点,并且现在在左端点/右端点的最小时间

转移显然可以 O ( 1 ) O(1) O(1)往两侧扩展,于是复杂度 O ( n 3 ) O(n^3) O(n3)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
template<typename T>inline void write(T x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
template<typename T>inline void pr1(T x){write(x);putchar(' ');}
template<typename T>inline void pr2(T x){write(x);putchar('\n');}
const int MAXN=205;
int f[2*MAXN][2*MAXN][MAXN][2],n,a[2*MAXN],T[2*MAXN],L;
void chkmin(int &x,int y){x=x<y?x:y;}
int dis(int x,int y){return abs(a[x]-a[y]);}
bool ok(int u,int TT){return T[u]>=TT;}
int main()
{
	memset(f,63,sizeof(f));int INF=f[0][0][0][0];
	n=read();L=read();
	for(int i=1;i<=n;i++)a[i+n]=read(),a[i]=-(L-a[i+n]);
	for(int i=1;i<=n;i++)T[i+n]=T[i]=read();
	for(int i=1;i<=2*n;i++)f[i][i][T[i]>=abs(a[i])][0]=abs(a[i]);
	for(int l=1;l<n;l++)for(int x=1;x+l-1<=2*n;x++)
	{
		int y=x+l-1;
		for(int k=0;k<=l;k++)for(int u=0;u<2;u++)if(f[x][y][k][u]!=INF)
		{
			int now=(u==0?x:y);
			if(x-1&&dis(now,x-1)<=INF)chkmin(f[x-1][y][k+ok(x-1,f[x][y][k][u]+dis(now,x-1))][0],f[x][y][k][u]+dis(now,x-1));
			if(y+1<=2*n&&dis(now,y+1)<=INF)chkmin(f[x][y+1][k+ok(y+1,f[x][y][k][u]+dis(now,y+1))][1],f[x][y][k][u]+dis(now,y+1));
		}
	}
	for(int k=n;k>=0;k--)for(int x=1;x+k-1<=2*n;x++)for(int y=x+k-1;y<=2*n;y++)
		if(f[x][y][k][0]!=INF||f[x][y][k][1]!=INF)
			return pr2(k),0;
	pr2(0);
	return 0;
}

loj#3255. 「JOI 2020 Final」奥运公交

枚举哪条边发生了置换

考虑我们需要求出的是 S → T S\rightarrow T ST的最短路,可以分为两种来讨论。第一种是必须经过反转的边,第二种是必须不经过翻转的边。对于边 ( x , y , c ) (x,y,c) (x,y,c),我们需要求出的是 S S S y y y不经过 ( x , y , c ) (x,y,c) (x,y,c)的最短路,以及 x x x T T T不经过 ( x , y , c ) (x,y,c) (x,y,c)的最短路。还有 S S S直接到 T T T不经过 ( x , y , c ) (x,y,c) (x,y,c)的最短路。考虑使用最短路树来进行计算,显然若一条边不在最短路树上,那么删去他后所有点的最短路不变。否则我们可以直接暴力对整张图跑一次最短路来求的全新的最短路数组

对于 T → S T\rightarrow S TS的最短路,如上一般讨论即可。由于最短路树边数在 O ( n ) O(n) O(n)级别,所以这个算法的复杂度为 O ( n K ) O(nK) O(nK),其中 K K K是最短路的复杂度。发现图是稠密图,于是直接跑 O ( n 2 ) O(n^2) O(n2) d i j k s t r a dijkstra dijkstra即可在 O ( n 3 ) O(n^3) O(n3)的时间内解决这个问题

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
template<typename T>inline void write(T x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
template<typename T>inline void pr1(T x){write(x);putchar(' ');}
template<typename T>inline void pr2(T x){write(x);putchar('\n');}
const int MAXN=205;
const int INF=1e9;
int n,m;
struct edge{int x,y,c,d,next;}E[2*MAXN*MAXN];
struct Graph2
{
	edge a[2*MAXN*MAXN];int len,last[MAXN];
	int dist[MAXN],vis[MAXN],prek[MAXN],is[2*MAXN*MAXN];
	void ins(int x,int y,int c,int d){len++;a[len].x=x;a[len].y=y;a[len].c=c;a[len].d=d;a[len].next=last[x];last[x]=len;}
	void dij(int S)
	{
		memset(dist,63,sizeof(dist));dist[S]=0;
		for(int T=1;T<=n;T++)
		{
			int x=0;for(int i=1;i<=n;i++)if(!vis[i]&&(!x||dist[i]<dist[x]))x=i;vis[x]=1;
			for(int k=last[x];k;k=a[k].next)
			{
				int y=a[k].y;
				if(dist[y]>dist[x]+a[k].c)dist[y]=dist[x]+a[k].c,prek[y]=k;
			}
		}
		for(int i=1;i<=n;i++)is[prek[i]]=1;
	}
}G1,G2,G3,G4;
struct Graph1
{
	vector<pii> vec[MAXN];
	int dist[MAXN],vis[MAXN];
	void init(){for(int i=1;i<=n;i++)vec[i].clear(),dist[i]=INF,vis[i]=0;}
	void ins(int x,int y,int c){vec[x].emplace_back(mp(y,c));}
	void dij(int S)
	{
		dist[S]=0;
		for(int T=1;T<=n;T++)
		{
			int S=0;for(int i=1;i<=n;i++)if(!vis[i]&&(!S||dist[i]<dist[S]))S=i;
			vis[S]=1;for(auto p:vec[S])dist[p.first]=min(dist[p.first],dist[S]+p.second);
		}
	}
}newG1,newG2,newG3,newG4;
int work1(int id)
{
	if(!G1.is[id])return 1;
	newG1.init();
	for(int i=1;i<=m;i++)if(i!=id)newG1.ins(E[i].x,E[i].y,E[i].c);
	newG1.dij(1);return 5;
}
int work2(int id)
{
	if(!G2.is[id])return 2;
	newG2.init();
	for(int i=1;i<=m;i++)if(i!=id)newG2.ins(E[i].y,E[i].x,E[i].c);
	newG2.dij(n);return 6;
}
int work3(int id)
{
	if(!G3.is[id])return 3;
	newG3.init();
	for(int i=1;i<=m;i++)if(i!=id)newG3.ins(E[i].y,E[i].x,E[i].c);
	newG3.dij(1);return 7;
}
int work4(int id)
{
	if(!G4.is[id])return 4;
	newG4.init();
	for(int i=1;i<=m;i++)if(i!=id)newG4.ins(E[i].x,E[i].y,E[i].c);
	newG4.dij(n);return 8;
}
int gi(int o,int x)
{
	if(o==1)return G1.dist[x];
	else if(o==2)return G2.dist[x];
	else if(o==3)return G3.dist[x];
	else if(o==4)return G4.dist[x];
	else if(o==5)return newG1.dist[x];
	else if(o==6)return newG2.dist[x];
	else if(o==7)return newG3.dist[x];
	else return newG4.dist[x];
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)E[i].x=read(),E[i].y=read(),E[i].c=read(),E[i].d=read();
	for(int i=1;i<=m;i++)
	{
		G1.ins(E[i].x,E[i].y,E[i].c,E[i].d);//1正 
		G2.ins(E[i].y,E[i].x,E[i].c,E[i].d);//n反 
		G3.ins(E[i].y,E[i].x,E[i].c,E[i].d);//1反 
		G4.ins(E[i].x,E[i].y,E[i].c,E[i].d);//n正 
	}G1.dij(1);G2.dij(n);G3.dij(1);G4.dij(n);LL ans=G1.dist[n]+G4.dist[1];
	for(int i=1;i<=m;i++)
	{
		int f1=work1(i),f2=work2(i),f3=work3(i),f4=work4(i);
		LL D1=min(gi(f1,n),gi(f1,E[i].y)+gi(f2,E[i].x)+E[i].c);
		LL D2=min(gi(f4,1),gi(f4,E[i].y)+gi(f3,E[i].x)+E[i].c);
		ans=min(ans,D1+D2+E[i].d);
	}if(ans<=INF)pr2(ans);
	else pr2(-1);
	return 0;
}

loj#3256. 「JOI 2020 Final」火灾

考虑我们维护每个人在每一时间段会变为什么。那么如果从 i i i变到 i + 1 i+1 i+1,这个会如何变化

如果将是什么划为序列 T T T,那么相当于先将所有 T i T_i Ti向后移动一位,然后所有位置对 a i a_i ai m a x max max

不难发现取 m a x max max的一定是一段前缀,所以可以考虑使用单调栈维护。这样询问是单点的东西我们就会做了。但是发现我们需要是询问一段区间,还需要将每个东西的 T T T序列给直接求和起来…似乎十分的不可做

先将询问拆分为 ( l − 1 , T ) (l-1,T) (l1,T) ( r , T ) (r,T) (r,T),此时显然只需要将一个前缀的 T T T序列求和起来再单点求值即可。问题在于这个求和。发现对于一段极长相同区间 ( l , r , T i ) (l,r,T_i) (l,r,Ti),假设他在此后的任意时刻均不会被覆盖的话,假定在某个时刻他移动到了某个 i i i的后面,那么对于这个原本 i > r i>r i>r的位置的贡献显然是 ( r − l + 1 ) × T i (r-l+1)\times T_i (rl+1)×Ti,已经在区间中的贡献则为 ( i − l + 1 ) × T i (i-l+1)\times T_i (il+1)×Ti

所以我们考虑先给后面把贡献全部给算上了,在询问的时候减去所有还没有经过我这个地方的贡献。不难发现显然是连续一段的 ( r i − l i + 1 ) × T i (r_i-l_i+1)\times T_i (rili+1)×Ti与一段的 ( i − l i + 1 ) × T i (i-l_i+1)\times T_i (ili+1)×Ti

于是可以考虑使用维护所有位置不乘 i i i的所有系数和与乘上 i i i的所有系数和,在 p o p pop pop掉一段区间的时候直接将他对于后面的贡献全部减掉即可。所有操作都可以视作区间加与单点求值,可以使用树状数组差分后在小常数 n log ⁡ n n\log n nlogn的时间内解决这个问题

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
template<typename T>inline void write(T x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
template<typename T>inline void pr1(T x){write(x);putchar(' ');}
template<typename T>inline void pr2(T x){write(x);putchar('\n');}
const int MAXN=200005;
struct ask
{
	int T,o,id;ask(){}
	ask(int _T,int _o,int _id){T=_T;o=_o;id=_id;}
};
vector<ask> vec[MAXN];
int n,m;
struct bittree
{
	LL bit[MAXN];
	int lowbit(int x){return x&-x;}
	void modify(int x,LL c){for(;x<=n;x+=lowbit(x))bit[x]+=c;}
	LL qry(int x){LL ret=0;for(;x>=1;x-=lowbit(x))ret+=bit[x];return ret;}
	void add(int l,int r,LL val){modify(l,val);modify(r+1,-val);}
}bi[2];//0 no*i 1 *i 
int a[MAXN];LL answer[MAXN];
int sta[MAXN],tp,L[MAXN],R;LL pres[MAXN];
void pop()
{
	int fl=L[tp]+R,fr=min(n,L[tp-1]-1+R);
	if(fl>n)return ;
	bi[0].add(fr+1,n,1LL*-(fr-fl+1)*sta[tp]);
	bi[0].add(fl,fr,1LL*(fl-1)*sta[tp]);bi[1].add(fl,fr,-sta[tp]);
}
int main()
{
	n=read()+1;m=read();
	for(int i=1;i<n;i++)a[i]=read();
	for(int i=1;i<=m;i++)
	{
		int T=read()+1,l=read(),r=read();
		vec[r].emplace_back(ask(T,1,i));vec[l-1].emplace_back(ask(T,-1,i));
	}L[0]=n+1;
	for(int i=1;i<n;i++)
	{
		while(tp&&sta[tp]<=a[i])pop(),--tp;
		if(!tp)
		{
			++tp;L[tp]=1-R;L[0]=n+1-R;sta[tp]=a[i];
			bi[1].add(1,n,sta[tp]);pres[tp]=1LL*n*sta[i];
		}
		else
		{
			int fl,fr;
			++tp;L[tp]=1-R;sta[tp]=a[i];
			fl=L[tp]+R,fr=min(n,L[tp-1]-1+R);
			bi[0].add(fr+1,n,1LL*(fr-fl+1)*sta[tp]);
			bi[0].add(fl,fr,1LL*-(fl-1)*sta[tp]);bi[1].add(fl,fr,sta[tp]);
			pres[tp]=pres[tp-1]+1LL*(L[tp-1]-L[tp])*sta[tp];
		}
		for(auto p:vec[i])
		{
			LL u1=bi[0].qry(p.T),u2=1LL*bi[1].qry(p.T)*p.T;
			int l=1,r=tp,num=-1;
			while(l<=r)
			{
				int mid=(l+r)/2;
				if(L[mid]+R<=p.T)num=mid,r=mid-1;
				else l=mid+1;
			}
			u1+=u2;
			u1-=pres[tp]-pres[num];u1-=1LL*(p.T-(L[num]+R))*sta[num];
			answer[p.id]+=u1*p.o;
		}++R;
	}
	for(int i=1;i<=m;i++)pr2(answer[i]);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值