zoukankan      html  css  js  c++  java
  • 2021CCPC网络赛(重赛)题解

    自己还是太菜了,五个小时一个题也没磕出来,还是队友牛逼!...

    Primality Test

    先看这个题,可能一上去就被(frac{f(x)+f(f(x))}{2})向下取整吓住了,但仔细想想,(f(x))(f(f(x)))不是相邻的质数吗?那么除2,向下取整,落点一定在两者之间,那么一定是合数。当然除了2,3的特例,这种特判下即可。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    int main()
    {
    	int T;cin>>T;
    	while(T--)
    	{
    		ll x;cin>>x;
    		if(x==1) cout<<"YES"<<endl;
    		else cout<<"NO"<<endl;	
    	}
    	return 0; 
    } 
    

    Nun Heh Heh Aaaaaaaaaaa

    为什么这么简单的题,我当初看了那么长的时间都没看出来。
    发现这个题的主要难点在于以当前i(c[i]='h')为结尾的字串为nunhehheh的方案数。我们大胆的设状态,仔细考虑dp所代表的的集合,以及进行转移。设f[i][j]表示前i位,其中第i位匹配到nunhehheh的第j位的方案数。那么若c[i]=s[j],我们考虑枚举上一个字符也就是s[j-1]出现的位置,f[i][j]=f[k][j-1].其中c[k]==s[j-1].但这种方法显然是O(n^2)的。考虑转移时,其实f[k][j-1],只要是j-1即可,我们大可以用一个数组g[j]表示前i个字符中以某个点为j结尾的方案数。这样的话转移时就是O(1)的。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=1e5+10,P=998244353;
    int T,n,f[N][12],cnt[N],g[12];
    char c[N];
    string s; 
    inline ll power(ll x,ll y)
    {
    	ll ans=1;
    	while(y)
    	{
    		if(y&1) ans=ans*x%P;
    		y>>=1;
    		x=x*x%P;
    	}
    	return ans%P;
    }
    
    inline void clear()
    {
    	for(int i=0;i<=n+1;++i) 
    	{
    		cnt[i]=0;
    		for(int j=0;j<=10;++j) f[i][j]=0;	
    	}
    	memset(g,0,sizeof(g));
    }
    
    int main()
    {
    	//freopen("1.in","r",stdin);
    	scanf("%d",&T);
    	s="2nunhehheh";
    	while(T--)
    	{
    		scanf("%s",c+1);
    		n=strlen(c+1);
    		clear();
    		for(int i=n;i>=1;--i) 
    		{
    			cnt[i]=cnt[i+1];
    			if(c[i]=='a') cnt[i]++;
    		}
    		ll ans=0;
    		for(int i=1;i<=n;++i)
    		{
    			for(int j=9;j>=1;--j)//匹配nunhehheh的每一位。 
    			{
    				if(c[i]==s[j])
    				{
    					f[i][j]=g[j-1];
    					if(j==1) f[i][j]=1;
    					g[j]=(g[j]+f[i][j])%P;
    					if(j==9) ans=(ans+(ll)f[i][j]*(power(2,cnt[i])-1)%P)%P;
    				}
    			}
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    Monopoly

    哎呀,这个题,感觉自己当时的思路已经很接近了,但是还是输给了对于分类讨论的复杂性的恐惧,加上当时没有一个完整的思路进行支撑,就弃疗了。
    首先我们设(S)为整个序列的和,(s_i)为第i位的前缀和,那么可以有这个式子(s_i+kS=x(k>=0,1leq ileq n)),其中只有(S,x)已知,我们做适当的调整,(kS=x-s_i),这样的话(x-s_i)必须是(S)的正整数(以及0)的倍数。那么可以想到(x,s_i)(S)同余,也就是说余数相同。这样的话,我们可以对(s_i)根据对(S)的余数进行分类,每次询问的x只在模数相同的一类中找。接下来考虑最小化(i+k*n)的值,这个时候我们可以讨论S>0,那么为了满足倍数的关系,必须满足(s_ileq x),同时为了k足够小,我们需要找到的(s_i)足够大,(你把这个过程放在数轴上想)。也就是小于等于x的最大值。这不是二分吗?我们再在每一类中进行排序,直接二分查找即可。接下来考虑S<0的情况,我们可以将序列中的每个数取反,再将x取反即可。注意当S=0时,系统会报错,单独讨论这种情况。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=1e5+10; 
    int T,n,m,num;
    map<ll,int>mp;//map表示序列号。 
    map<ll,int>id[N];//余数为i的数字为j的最小的i。 
    vector<ll>ve[N];
    ll s[N];
    
    inline void clear()
    {
    	mp.clear();num=0;
    	for(int i=0;i<=n;++i) 
    	{
    		s[i]=0;
    		id[i].clear();	
    		ve[i].clear();
    	}
    }
    
    inline void solve()
    {
    	mp[0]=0;
    	for(int i=1;i<=n;++i)
    	{
    		if(mp.find(s[i])==mp.end()) 
    			mp[s[i]]=i;
    	}
    	for(int i=1;i<=m;++i)
    	{
    		ll x;scanf("%lld",&x);
    		if(mp.find(x)==mp.end()) puts("-1");
    		else printf("%d
    ",mp[x]);
    	}
    }
    
    int main()
    {
    //	freopen("1.in","r",stdin);
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d%d",&n,&m);
    		clear();
    		for(int i=1;i<=n;++i)
    		{
    			ll x;scanf("%lld",&x);
    			s[i]=s[i-1]+x;
    		}
    		if(s[n]==0) {solve();continue;}
    		int op=1;if(s[n]<0) op=-1;
    		ll S=s[n]*op;
    		for(int i=0;i<=n;++i)
    		{
    			s[i]*=op;
    			ll ps=(s[i]%S+S)%S;
    			if(mp.find(ps)==mp.end()) mp[ps]=++num;
    			if(id[mp[ps]].find(s[i])==id[mp[ps]].end()) 
    			{
    				id[mp[ps]][s[i]]=i;
    				ve[mp[ps]].push_back(s[i]);
    			}
    		}
    		for(int i=1;i<=num;++i) sort(ve[i].begin(),ve[i].end());
    		for(int i=1;i<=m;++i)
    		{
    			ll x;scanf("%lld",&x);
    			x*=op;
    			if(x==0) {puts("0");continue;}
    			ll ps=(x%S+S)%S;
    			if(mp.find(ps)==mp.end()||ve[mp[ps]][0]>x) {puts("-1");continue;}
    			int j=mp[ps];
    			int k=upper_bound(ve[j].begin(),ve[j].end(),x)-ve[j].begin()-1;
    			ll so=ve[j][k];
    			ll ans=id[j][so]+((x-so)/S)*n;
    			printf("%lld
    ",ans);
    		}
    	}
    	return 0;
    }
    

    Jumping Monkey

    这个题当初还是队友做出来的,自己主要是思路就没想到那一块去。由于每次从一个点出发一直跳,跳的最多的点的个数,想想其实问你的就是从这个点出发能跳到哪些点去,因为这些点我们可以按点权从小到大排序依次跳就完事了。当初困在了DP的思维上,想不出来好的转移方式。后来看了看题解大大的做法,其实是图论的知识吧,也不对就是思维的问题吧。我们考虑先将所有的点从小到大排序,依次考虑每个点能否到达点i,这样点i一定比之前的点权大,只要联通即可。也就是重新建图加边的问题。考虑当前是一个空白的图,我们依次将每个点加进去,考虑哪些点能到达当前这个点的话,就是连通块的问题,若当前点能够连到某个连通块,则这个连通块内的所有点都能到达这个点,那么这些点的答案都加1.然后将这个点及其连到的点合并成一个新的连通块即可。考虑整个需要我们维护的操作就是加上点,连通块整个的值加1,合并连通块。对于连通块的做法,我只会并查集,其实并查集就是维护连通块是否联通的问题,顺带记录一些信息。接下来就是这个题的精妙之处,我们发现,我们每次都是将一个连通块的值都加1,我们可以将加进去的点i当做根节点,这样的话那些连通块的深度就加1,符合我们的要求,我们只需要将点i向原本这个连通块的根节点连边即可。但这样不就打乱了原本的土的结构了吗?其实我们没必要维护原本的土的结构,我们还需要在一个点i加进去之后,那些连通块的点都可以加1就行。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int T,n,f[N],a[N],b[N],d[N];
    vector<int>son[N]; 
    vector<int>bian[N];
    map<pair<int,int>,bool>mp;
    
    inline void clear()
    {
    	mp.clear();
    	for(int i=1;i<=n;++i) 
    	{
    		son[i].clear();
    		bian[i].clear();
    		f[i]=i;b[i]=i;	
    		d[i]=0;	
    	}
    }
    
    inline bool cmp(int x,int y) {return a[x]<a[y];}
    
    inline int getf(int x) {return f[x]==x?x:f[x]=getf(f[x]);}
    
    inline void dfs(int x)
    {
    	for(auto y:bian[x])
    	{
    		d[y]=d[x]+1;
    		dfs(y);
    	}
    }
    
    int main()
    {
    //	freopen("1.in","r",stdin);
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d",&n);
    		clear();
    		for(int i=1;i<n;++i)
    		{
    			int x,y;scanf("%d%d",&x,&y);
    			son[x].push_back(y);
    			son[y].push_back(x);
    		}
    		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    		sort(b+1,b+n+1,cmp);
    		for(int i=1;i<=n;++i)//依次加入每一个点。
    		{
    			int x=b[i];
    			for(auto y:son[x])//遍历i的每一个出边
    			{
    				if(a[y]>a[x]) continue;
    				int t=getf(y);
    				if(mp.find({x,t})==mp.end())
    				{
    					mp[{x,t}]=1;
    					bian[x].push_back(t);
    					f[t]=x;
    				}
    			} 
    		}
    		d[b[n]]=1;
    		dfs(b[n]);
    		for(int i=1;i<=n;++i) printf("%d
    ",d[i]);
    	}
    	return 0;
    } 
    

    Public Transport System

    这个题当初也是想了好久没出来,显然因为如果(a_i>a_{i-1})的话我们走当前的边权的值就为(a_i-b_i),这就使得我们必须记录当前点的上一条边是哪一条,想了想,其实所有的状态数也不多,即m个状态,毕竟只有m条边,每条边对应一个状态。但这个记录就只能用map实现,如果再跑dijkstra总的复杂度为O(mlognlogm),m的范围为(1.2 imes10^6),这算出来,(6 imes10^9),好吧,我觉得出题人肯定就是专门卡这个暴力的....
    瞅瞅题解大大的做法,原来是要重新建图,又是建图的问题,这种题不是只在网络流中考察吗?好吧,这次确实拓了眼界。首先先将两种边分开,因为两种边权的图,毕竟没有单边权的方便。考虑边权(a_i)的边,我们没什么限制条件,但对于边权为(a_i-b_i)的边,我们必须满足一定条件,也就是前一条边的(a_i)必须小于当前的(a_i)。我们可以发现由于一个之前的边对应的可走的边权为(a_i-b_i)的边的(a_i)是在一个区间的,所以这就给我们的优化带来了可能。(不是我说的,是题解说的。)具体的,我们可以这样做:首先设d为当前点x的出度,我们将x拆分成d+1个点,((x_0,x_1,x_2,...,x_d)),让他们分别管理这些(a_i-b_i)出边,其中所有边权为(a_i)的边由(x_0)管理。我们将x所有边权为(a_i-b_i)的出边按照(a_i)的从大到小排序,接下来将他们依次交给(x_i)管理(也就是(x_i)连排过序后为i的边)。我们接下来i从0到d-1,从(x_{i+1})(x_{i})连边权为0的边。这样由于提前排过序,能从小的(a_i)(a_i-b_i)的特殊边,一定能从大的(a_i)(a_i-b_i)的特殊边。之后我们只需要根据每个入边找到相对应能走的最大的(x_i),满足它所管理的边的(a_i)大于他的入边即可。
    具体的如下图:
    image
    image
    这样,一共的点为n+m,一共的边为2*m,即使m是(1.2 imes 10^6)的数据量,我们跑dijkstra也完全没有问题。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=4e5+10;
    int du[N],n,m,l[N],link[N],tot,vis[N];
    ll dis[N];
    //l[i]记录每个点拆出来的d+1个点的第一个点的编号。 
    vector<int>son[N];//记录每个点,对应的边的编号。 
    struct bian{int x,y,A,B;}b[N];
    struct wy{int y,v,next;}a[N<<1];
    priority_queue<pair<ll,int> >q;
    
    inline void add(int x,int y,int v)
    {
    	a[++tot].y=y;a[tot].v=v;a[tot].next=link[x];link[x]=tot;
    }
    
    inline void clear()
    {
    	tot=0;
        for(int i=1;i<=n+m;++i) link[i]=0;
        for(int i=1;i<=n;++i) son[i].clear(),du[i]=0;
    }
    
    inline bool cmp(int x,int y)
    {
    	return b[x].A>b[y].A;
    }
    
    inline int find(int x,int A)//有一个ai的入边应该连向点x中的哪个点
    {
    	if(!du[x]||A>=b[son[x][0]].A) return l[x];//返回x0的情况。
    	int L=0,R=du[x]-1;
    	while(L<R)//在x的边中查找>A的最小的边。 
    	{
    		int mid=L+R+1>>1;
    		if(b[son[x][mid]].A>A) L=mid;
    		else R=mid-1;
    	}
    	return l[x]+L+1;
    } 
    
    inline void dijkstra()
    {
    	while(q.size()) q.pop();
    	for(int i=1;i<=n+m;++i)
    	{
    		dis[i]=1e18;
    		vis[i]=0;
    	}
    	dis[l[1]]=0;q.push({0,l[1]});
    	while(!q.empty())
    	{
    		int x=q.top().second;q.pop();
    		if(vis[x]) continue;
    		vis[x]=1;
    		for(int i=link[x];i;i=a[i].next)
    		{
    			int y=a[i].y;
    			if(dis[x]+a[i].v<dis[y])
    			{
    				dis[y]=dis[x]+a[i].v;
    				q.push({-dis[y],y});
    			}
    		}
    	}
    }
    
    int main()
    {
    //	freopen("1.in","r",stdin);
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d%d",&n,&m);
    		clear();
    		for(int i=1;i<=m;++i)
    		{
    			scanf("%d%d%d%d",&b[i].x,&b[i].y,&b[i].A,&b[i].B);
    			son[b[i].x].push_back(i);
    			du[b[i].x]++;
    		} 
    		for(int i=1;i<=n;++i)
    		{
    			if(son[i].size()) 
    				sort(son[i].begin(),son[i].end(),cmp);
    		}
    		int num=0;
    		for(int i=1;i<=n;++i)  //给每个点分配编号且处理内部的边。
    		{
    			l[i]=++num; //l[i] - l[i]+du[i]是这个点所有拆出来的点的编号。 
    			for(int j=du[i];j>=1;--j) add(l[i]+j,l[i]+j-1,0);
    			num+=du[i];
    		} 
    		for(int i=1;i<=n;++i)//处理点i的所有边
    		{
    			int js=son[i].size();
    			for(int j=0;j<js;++j)//枚举i的所有出边 
    			{
    				int id=son[i][j];
    				int y=find(b[id].y,b[id].A);
    				add(l[i],y,b[id].A);//先处理边权为ai的。 
    				add(l[i]+j+1,y,b[id].A-b[id].B);
    			}
    		} 
    		dijkstra();
    		for(int i=1;i<=n;++i) 
    		{
    			if(dis[l[i]]==1e18) dis[l[i]]=-1;
    			printf("%lld",dis[l[i]]);	
    			if(i!=n) printf(" "); 
    		}
    		printf("
    ");
    	}
    	return 0;
    }
    
  • 相关阅读:
    Vue组件库elementUI 在el-row 或 el-col 上使用@click无效失效,
    js判断客户端是手机端还是PC端
    IOS上微信在输入框弹出键盘后,页面不恢复,下方有留白,有弹窗弹出时页面内容感应区域错位
    vue打包问题:Tip: built files are meant to be served over an HTTP server.
    在vue项目npm run build后,index.html中引入css和js 报MIME type问题
    Vue项目中如何使用less(添加less依赖)
    Mac 下永久路由的添加 & Mac 校园网连接教程
    JetBrains RubyMine 2019 for Mac(Ruby代码编辑器) 这些功能你用了几个?
    代码片段管理软件Snippetslab mac版软件测评
    十分钟玩转 XMind 中的多种思维结构
  • 原文地址:https://www.cnblogs.com/gcfer/p/15398628.html
Copyright © 2011-2022 走看看