zoukankan      html  css  js  c++  java
  • JOI 2020 Final 题解

    T1. 只不过是长的领带

    大水题,把 (a_i,b_i) 从小到大排序。
    发现最优方案只可能是大的 (a_i) 跟大的 (b_i) 匹配,小的 (a_i) 与小的 (b_i) 匹配。
    故前缀后缀各算一遍最大值就行了。

    #include <bits/stdc++.h>
    using namespace std;
    #define fi first
    #define se second
    #define fz(i,a,b) for(int i=a;i<=b;i++)
    #define fd(i,a,b) for(int i=a;i>=b;i--)
    #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
    #define fill0(a) memset(a,0,sizeof(a))
    #define fill1(a) memset(a,-1,sizeof(a))
    #define fillbig(a) memset(a,63,sizeof(a))
    #define pb push_back
    #define ppb pop_back
    #define mp make_pair
    typedef pair<int,int> pii;
    typedef long long ll;
    const int MAXN=2e5+5;
    int n,b[MAXN];pii a[MAXN];
    int pre[MAXN],suf[MAXN],ans[MAXN];
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n+1;i++) scanf("%d",&a[i].fi),a[i].se=i;
    	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    	sort(a+1,a+n+2);sort(b+1,b+n+1);
    	for(int i=1;i<=n;i++) pre[i]=max(pre[i-1],max(a[i].fi-b[i],0));
    	for(int i=n;i;i--) suf[i]=max(suf[i+1],max(a[i+1].fi-b[i],0));
    	for(int i=1;i<=n+1;i++) ans[a[i].se]=max(pre[i-1],suf[i]);
    	for(int i=1;i<=n+1;i++) printf("%d ",ans[i]);
    	return 0;
    }
    

    T2. JJOOII

    发现 (1) 操作其实是删除 (s) 的一个前缀,(2) 操作其实是删除 (s) 的一个后缀。
    预处理出 (nxt_{i,0/1/2}) 表示从 (i) 开始最少需要扩展到什么位置才能存在 (k) 个 'J'/'O'/'I',双针扫一遍。
    我们枚举这个前缀删了多少个字符,用 (nxt) 数组求出在这种情况下后缀最多删了多少个字符,更新答案即可。
    时间复杂度 (mathcal O(n))

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN=2e5+5;
    char s[MAXN];
    int n,k,nxt[MAXN][3]; 
    int main(){
    	scanf("%d%d%s",&n,&k,s+1);
    	int cur=1,num=0;
    	while(cur<=n&&num<k) num+=(s[cur++]=='J');cur--;
    	for(int i=1;i<=n+1;i++){
    		nxt[i][0]=cur;
    		if(s[i]=='J'){
    			if(cur!=n+1) cur++;
    			while(cur<=n&&s[cur]!='J') cur++;
    		}
    	}
    	cur=1,num=0;
    	while(cur<=n&&num<k) num+=(s[cur++]=='O');cur--;
    	for(int i=1;i<=n+1;i++){
    		nxt[i][1]=cur;
    		if(s[i]=='O'){
    			if(cur!=n+1) cur++;
    			while(cur<=n&&s[cur]!='O') cur++;
    		}
    	}
    	cur=1,num=0;
    	while(cur<=n&&num<k) num+=(s[cur++]=='I');cur--;
    	for(int i=1;i<=n+1;i++){
    		nxt[i][2]=cur;
    		if(s[i]=='I'){
    			if(cur!=n+1) cur++;
    			while(cur<=n&&s[cur]!='I') cur++;
    		}
    	}
    	int ans=n+1;
    	for(int i=1;i<=n;i++){
    		int r=nxt[i][0];
    		if(r!=n+1) r=nxt[r+1][1];
    		if(r!=n+1) r=nxt[r+1][2];
    		if(r!=n+1) ans=min(ans,r-i+1-(k*3));
    	} printf("%d
    ",(ans==n+1)?(-1):ans);
    }
    

    T3. 集邮比赛

    (dp)(dp_{l,r,k,0/1}) 表示逆时针收集了 (l) 个,顺时针收集了 (r) 个,当前在左边还是右边所需的最小时间。
    转移从 (dp_{l,r}) 更新到 (dp_{l+1,r})(dp_{l,r+1}),看时间是否 (leq t_l) 判断能否收集。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    template<typename T> void chkmin(T &a,T b){if(a>b) a=b;}
    template<typename T> void chkmax(T &a,T b){if(a<b) a=b;}
    const int MAXN=200+5;
    int n,L,x[MAXN],t[MAXN],s[MAXN];
    ll dp[MAXN][MAXN][MAXN][2];
    int main(){
    	scanf("%d%d",&n,&L);
    	for(int i=1;i<=n;i++) scanf("%d",&x[i]);
    	for(int i=1;i<=n;i++) scanf("%d",&t[i]);
    	memset(dp,63,sizeof(dp));dp[0][0][0][0]=0;
    	for(int i=0;i<=n;i++) for(int j=0;j+i<n;j++){
    		for(int k=0;k<=i+j;k++){
    			chkmin(dp[i+1][j][k+((dp[i][j][k][0]+x[i+1]-x[i])<=t[i+1])][0],dp[i][j][k][0]+x[i+1]-x[i]);
    			chkmin(dp[i+1][j][k+((dp[i][j][k][1]+L-x[n-j+1]+x[i+1])<=t[i+1])][0],dp[i][j][k][1]+L-x[n-j+1]+x[i+1]);
    			chkmin(dp[i][j+1][k+((dp[i][j][k][1]+x[n-j+1]-x[n-j])<=t[n-j])][1],dp[i][j][k][1]+x[n-j+1]-x[n-j]);
    			chkmin(dp[i][j+1][k+((dp[i][j][k][0]+L-x[n-j]+x[i])<=t[n-j])][1],dp[i][j][k][0]+L-x[n-j]+x[i]);
    		}
    	}
    	int ans=0;
    	for(int i=0;i<=n;i++) for(int j=0;j+i<=n;j++) for(int k=0;k<=i+j;k++){
    		if(dp[i][j][k][0]<1e18) chkmax(ans,k);
    		if(dp[i][j][k][1]<1e18) chkmax(ans,k);
    	} printf("%d
    ",ans);
    }
    

    T4. 奥运公交

    这题想了好久。。。我觉得我要爆了。
    首先枚举 (m),计算翻转每条边后 (1 o n)(n o 1) 的最短路。
    这里以 (1 o n) 为例,(n o 1) 同理。
    翻转后 (1 o n) 的最短路可以分为两种情况:经过这条边和不经过这条边。也就是不经过这条边的 (1 o n) 的最短路,与不经过这条边的 (1 o v) 的最短路 (+c_i+) 不经过这条边的 (u o n) 的最短路的较小值。
    不难发现不经过这条边的 (u o n) 的最短路的较小值可以通过建反图归约到前一类。
    于是本题就变为,如何求出不经过某条边的从源点出发到达某个点的最短路。
    不难发现,不在最短路树上的边不会对答案造成影响,故如果这条边不在最短路上,那就返回原始的最短路。
    否则,最短路树上的边顶多 (n-1) 个,你重新跑 (n-1) 次 dijkstra 也能接受。
    怎样求最短路树?很简单,你记录每个点的前驱,它们形成的就是一个最短路树。
    最后,堆优化的 dijkstra 是 (mlog m) 的,朴素 dijkstra 是 (n^2) 的。由于本题的图是稠密图,堆优化的 dijkstra 反而跑不过朴素的 dijkstra。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=200+5;
    const int MAXM=5e4+5;
    int n,m,u[MAXM],v[MAXM],c[MAXM],d[MAXM];
    struct graph{
    	int hd[MAXN],to[MAXM],nxt[MAXM],cst[MAXM],ec,from,pre[MAXN];
    	bool on[MAXM],vis[MAXN];
    	ll dist[MAXN],odist[MAXN];
    	void adde(int u,int v,int c){
    		to[++ec]=v;cst[ec]=c;nxt[ec]=hd[u];hd[u]=ec;
    	}
    	void dijkstra(int forbid){
    		memset(dist,63,sizeof(dist));
    		memset(pre,0,sizeof(pre));
    		memset(vis,0,sizeof(vis));
    		dist[from]=0;
    		for(int i=1;i<=n;i++){
    			ll mn=1e18;int x=n+1;
    			for(int j=1;j<=n;j++) if(dist[j]<mn&&!vis[j]) mn=dist[j],x=j;
    			if(x==n+1) break;vis[x]=1;
    			for(int e=hd[x];e;e=nxt[e]){
    				if(e==forbid) continue;
    				int y=to[e],z=cst[e];
    				if(dist[y]>dist[x]+z){
    					dist[y]=dist[x]+z;pre[y]=e;
    				}
    			}
    		}
    	}
    	void prework(){
    		dijkstra(0);
    		for(int i=1;i<=n;i++) odist[i]=dist[i];
    //		for(int i=1;i<=n;i++) printf("%lld ",odist[i]);printf("
    ");
    		for(int i=1;i<=n;i++) on[pre[i]]=1;
    	}
    	ll calc(int e,int x){
    		if(!on[e]) return odist[x];
    		dijkstra(e);return dist[x];
    	}
    } g[4];
    int main(){
    	scanf("%d%d",&n,&m);
    	g[0].from=g[2].from=1;g[1].from=g[3].from=n;
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d%d",&u[i],&v[i],&c[i],&d[i]);
    		g[0].adde(u[i],v[i],c[i]);g[1].adde(u[i],v[i],c[i]);
    		g[2].adde(v[i],u[i],c[i]);g[3].adde(v[i],u[i],c[i]);
    	}
    	for(int i=0;i<4;i++) g[i].prework();
    	ll ans=g[0].odist[n]+g[1].odist[1];
    	for(int i=1;i<=m;i++){
    		ans=min(ans,
    		min(g[0].calc(i,n),g[0].calc(i,v[i])+g[3].calc(i,u[i])+c[i])+
    		min(g[1].calc(i,1),g[1].calc(i,v[i])+g[2].calc(i,u[i])+c[i])+d[i]);
    	}
    	if(ans<1e18) printf("%lld
    ",ans);
    	else printf("-1
    ");
    	return 0;
    }
    

    T5. 火灾

    神仙题,待会儿写个题解

  • 相关阅读:
    layui + mvc + ajax 导出Excel功能
    PL/SQL Developer工具包和InstantClient连接Oracle 11g数据库
    .NET中JSON的序列化和反序列化的几种方式
    C# 编程中的堆栈(Stack)和队列(Queue)
    Oracle 数据库常用操作语句大全
    C#方法中参数ref和out的解析
    JS实现限行
    ajax+ashx 完美实现input file上传文件
    HTML5 学习
    Linux文件和目录操作管理命令
  • 原文地址:https://www.cnblogs.com/ET2006/p/JOI-2020-Final.html
Copyright © 2011-2022 走看看