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

    题目列表:https://loj.ac/problems/search?keyword=JOI+2018+Final

    T1 寒冬暖炉

    贪心

    暴力考虑每相邻两个人之间的间隔,从小到大选取即可

    #include<iostream>
    #include<string.h>
    #include<string>
    #include<stdio.h>
    #include<algorithm>
    #include<math.h>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    const int maxd=1000000007,N=100000;
    const double pi=acos(-1.0);
    typedef long long ll;
    int n,k,a[100100],x[100100];
    
    int read()
    {
    	int x=0,f=1;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;
    }
    
    bool cmp(int x,int y)
    {
    	return x>y;
    }
    
    int main()
    {
    	n=read();k=read();
    	int i;
    	for (i=1;i<=n;i++) a[i]=read();
    	if (k>=n) {printf("%d",n);return 0;}
    	for (i=1;i<n;i++) x[i]=(a[i+1]-a[i]-1);
    	sort(x+1,x+n);
    	int ans=n;
    	for (i=1;i<=n-k;i++) ans+=x[i];
    	printf("%d",ans);
    	return 0;
    }
    
    

    T2 美术展览

    贪心

    在确定了(A_{min})(A_{max})之后,我们显然会选取它们中间的所有数

    将所有的美术品按照(A)为主关键字排序

    将原式改为前缀和的形式

    [S-(A_{max}-A_{min})=S_r-S_{l-1}-Ar+A_l=(S_r-A_r)+(A_l-S_{l-1}) ]

    枚举(r),同时维护(A_l-S_{l-1})的最大值即可

    #include<iostream>
    #include<string.h>
    #include<string>
    #include<stdio.h>
    #include<algorithm>
    #include<math.h>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    const int maxd=1000000007,N=100000;
    const double pi=acos(-1.0);
    typedef long long ll;
    struct node{
    	ll a,b;
    }x[500100];
    int n;
    
    bool operator <(node p,node q)
    {
    	return p.a<q.a;
    }
    
    ll read()
    {
    	ll x=0,f=1;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 main()
    {
    	int i;
    	n=read();
    	for (i=1;i<=n;i++) {x[i].a=read();x[i].b=read();}
    	sort(x+1,x+1+n);
    	ll sum=0,pre=0,ans=0;
    	for (i=1;i<=n;i++)
    	{
    		pre=max(pre,-sum+x[i].a);
    		sum+=x[i].b;
    		ans=max(ans,pre+sum-x[i].a);
    	}
    	printf("%lld",ans);
    	return 0;
    }
    

    T3 团子制作

    dp

    我们考虑在团子的中心点统计答案

    由于中心点不在同一条对角线上的团子不可能重合,于是按照对角线dp

    每一次考虑当前的团子是不放/横放/竖放

    你看这个dp连数组都不用开

    #include<iostream>
    #include<string.h>
    #include<string>
    #include<stdio.h>
    #include<algorithm>
    #include<math.h>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    const int maxd=1000000007,N=100000;
    const double pi=acos(-1.0);
    typedef long long ll;
    int n,m;
    char s[3010][3010];
    
    int read()
    {
    	int x=0,f=1;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 getr(int x,int y)
    {
    	if (y-2<=0) return 0;
    	//cout << "now: " << x << " " << y << endl;
    	return ((s[x][y-1]=='R') && (s[x][y]=='G') && (s[x][y+1]=='W'));
    }
    
    int getC(int x,int y)
    {
    	if (x-2<=0) return 0;
    	return ((s[x-1][y]=='R') && (s[x][y]=='G') && (s[x+1][y]=='W'));
    }
    
    int main()
    {
    	n=read();m=read();
    	int i,j,k,ans=0;
    	for (i=1;i<=n;i++) scanf("%s",s[i]+1);
    	for (k=2;k<=n+m;k++)
    	{
    		int nx=min(k-1,n),ny=k-nx;
    		int pre0=0,pre1=0,pre2=0;
    		while ((nx>=1) && (ny<=m))
    		{
    			//cout << nx << " " << ny << endl;
    			int r=getr(nx,ny),c=getC(nx,ny);
    			int tmp=pre0;
    			//cout << r << " " << c <<endl;
    			pre0=max(pre0,max(pre1,pre2));
    			if (r) pre1=max(tmp,pre1)+1;
    			if (c) pre2=max(tmp,pre2)+1;
    			//cout << pre0 << " " << pre1 << " " << pre2 << endl; 
    			nx--;ny++;
    		}
    		//cout << endl;
    		ans+=max(max(pre0,pre1),pre2);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    T4 月票购买

    图论+DAG dp

    贪心的考虑,(s->t)(u->v)的路径如果要重合的话就一定会重合连续的一段

    我们将(s->t)的所有最短路径拎出来并且按照(s->t)的方向定向,那么这一定是一个DAG

    我们假设两条路径重合的是(x->y),那么我们就是求(dis(u,x)+dis(y,v))的最小值

    直接在DAG上跑即可,类似于tree dp

    #include<iostream>
    #include<string.h>
    #include<string>
    #include<stdio.h>
    #include<algorithm>
    #include<math.h>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    
    typedef long long ll;
    const ll maxd=(ll)1e18+7,N=100000;
    const double pi=acos(-1.0);
    struct node{
    	int to,nxt,cost;
    }sq[400400];
    
    struct heapnode{
    	int u;ll dis;
    };
    bool operator <(const heapnode &p,const heapnode &q)
    {
    	return p.dis>q.dis;
    }
    priority_queue<heapnode> q;
    int n,m,s,t,from,to,all=0,head[100100];
    ll dis_s[100100],dis_t[100100],dis_u[100100],dis_v[100100],
       mind_u[100100],mind_v[100100];
    bool vis[100100];
    
    int read()
    {
    	int x=0,f=1;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;
    }
    
    void add(int u,int v,int w)
    {
    	all++;sq[all].to=v;sq[all].cost=w;sq[all].nxt=head[u];head[u]=all;
    }
    
    void dijkstra(int s,ll *dis)
    {
    	int i;
    	for (i=1;i<=n;i++) dis[i]=maxd;
    	memset(vis,0,sizeof(vis));
    	dis[s]=0;q.push((heapnode){s,0});
    	while (!q.empty())
    	{
    		heapnode now=q.top();q.pop();
    		if (vis[now.u]) continue;
    		vis[now.u]=1;int u=now.u;
    		for (i=head[u];i;i=sq[i].nxt)
    		{
    			int v=sq[i].to;
    			if (dis[v]>dis[u]+sq[i].cost)
    			{
    				//cout << v << " ";
    				dis[v]=dis[u]+sq[i].cost;
    				q.push((heapnode){v,dis[v]});
    			}
    		}
    	}
    }
    
    void init()
    {
    	n=read();m=read();
    	s=read();t=read();from=read();to=read();
    	int i;
    	for (i=1;i<=m;i++)
    	{
    		int u=read(),v=read(),w=read();
    		add(u,v,w);add(v,u,w);
    	}
    	dijkstra(s,dis_s);
    	dijkstra(t,dis_t);
    	dijkstra(from,dis_u);
    	dijkstra(to,dis_v);
    	//for (i=1;i<=n;i++) cout << dis_v[i] << " ";cout << endl; 
    }
    
    void dfs(int u)
    {
    	vis[u]=1;
    	int i;
    	mind_u[u]=dis_u[u];
    	mind_v[u]=dis_v[u];
    	for (i=head[u];i;i=sq[i].nxt)
    	{
    		int v=sq[i].to;
    		if (dis_s[u]+dis_t[v]+sq[i].cost==dis_s[t])
    		{
    			if (!vis[v]) dfs(v);
    			if (mind_u[u]+mind_v[u]>min(mind_u[v],dis_u[u])+min(mind_v[v],dis_v[u]))
    			{
    				mind_u[u]=min(mind_u[v],dis_u[u]);
    				mind_v[u]=min(mind_v[v],dis_v[u]);
    			}
    		}
    	}
    }
    
    void work()
    {
    	memset(vis,0,sizeof(vis));
    	dfs(s);
    	printf("%lld",min(dis_u[to],mind_u[s]+mind_v[s]));
    }
    
    signed main()
    {
    	init();
    	work();
    	return 0;
    }
    

    T5 毒蛇越狱

    最暴力的想法就是枚举?的取值,时间复杂度(O(2^{cnt_?})),明显爆炸

    然后发现可以对所有0的位置或所有1的位置进行容斥,时间复杂度(O(2^{cnt_0}))(O(2^{cnt_1})),依然无法保证

    然后我们注意到(Lleq 20)

    由于抽屉原理我们知道(min(cnt_?,cnt_0,cnt_1)leq 6)

    于是复杂度瞬间有了保证

    具体容斥过程可以看程序头部的注释

    //sum[0][i]表示当i中的1恒定为1,剩下的位可0可1时的w之和 
    //sum[1][i]表示当i中的0恒定为0,剩下的位可0可1时的w之和
    //考虑容斥
    //当0的个数小于6时,总数为sum[0][p1],但是这样的话某些该为0的位上就是1,枚举p0子集,依照0变为1的个数容斥 
    //当1的个数小于6时,总数为sum[1][p2|p1],但是这样的话某些该为1的位上就是0,枚举p1子集,依照当前状态与p1的差容斥 
    #include<iostream>
    #include<string.h>
    #include<string>
    #include<stdio.h>
    #include<algorithm>
    #include<math.h>
    #include<vector>
    #include<queue>
    #include<map>
    using namespace std;
    const int maxd=1000000007,N=100000;
    const double pi=acos(-1.0);
    typedef long long ll;
    int l,q,n,cnt[1100000],sum[2][1100000],w[1100000];
    char s[1100000];
    
    int read()
    {
    	int x=0,f=1;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 main()
    {
    	//freopen("a.in","r",stdin);
    	//freopen("a.out","w",stdout);
    	l=read();q=read();
    	memset(sum,0,sizeof(sum));
    	scanf("%s",s);n=(1<<l);
    	int i,j;
    	for (i=0;i<n;i++)
    	{
    		w[i]=s[i]-'0';sum[0][i]=w[i];sum[1][i]=w[i];
    	}
    	for (i=0;i<l;i++)
    	{
    		for (j=0;j<n;j++)
    		{
    			if (j&(1<<i)) {sum[0][j^(1<<i)]+=sum[0][j];sum[1][j]+=sum[1][j^(1<<i)];}
    		}
    	}
    	for (i=1;i<n;i++) cnt[i]=cnt[i>>1]+(i&1);
    	while (q--)
    	{
    		scanf("%s",s);
    		int p0=0,p1=0,p2=0;
    		for (i=0;i<l;i++)
    		{
    			if (s[l-i-1]=='0') p0+=(1<<i);
    			else if (s[l-i-1]=='1') p1+=(1<<i);
    			else p2+=(1<<i);
    		}
    		//cout << p0 << " " << p1 << " " << p2 << endl;
    		ll ans=0;
    		if (cnt[p0]<=6)
    		{	
    			for (i=p0;;i=(i-1)&p0)
    			{
    				if (cnt[i]&1) ans-=sum[0][i|p1];
    				else ans+=sum[0][i|p1];
    				if (!i) break;
    			}
    		}
    		else if (cnt[p1]<=6)
    		{
    			for (i=p1;;i=(i-1)&p1)
    			{
    				if (cnt[i^p1]&1) ans-=sum[1][i|p2];
    				else ans+=sum[1][i|p2];
    				if (!i) break;
    			}
    		}
    		else if (cnt[p2]<=6)
    		{
    			for (i=p2;;i=(i-1)&p2)
    			{
    				ans+=w[i|p1];
    				if (!i) break;
    			}
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    	
    
  • 相关阅读:
    每天一个linux命令:head(15)
    用设计模式来替代if-else
    每天一个linux命令:less(14)
    每天一个linux命令:more(13)
    每天一个linux命令:nl(12)
    CDN是什么鬼
    ajax跨域问题
    PDO和MySQLi区别与选择?
    php 依赖注入 和 控制反转 php设计模式
    理解 PHP 依赖注入
  • 原文地址:https://www.cnblogs.com/encodetalker/p/10544541.html
Copyright © 2011-2022 走看看