当前位置:主页 > 查看内容

训练日记 | 2021.03.31 | 省赛选拔赛

发布时间:2021-04-25 00:00| 位朋友查看

简介:本次比赛由大四金牌团队出题 A - Array Permutation 题意 思维题。输入n现有m位的数组arraymn从[ 1n-m ]中随机生成x将[ 123…x ]排列到array后面组成新数组直到数组有n位。求对于特定的n有几种不同的数组 思路 ①模拟一下结果就是 2 n-1 . ②这题要用 快速幂……

本次比赛由大四金牌团队出题

A - Array Permutation

题目
题目
题意: 思维题。输入n,现有m位的数组array(m<n),从[ 1,n-m ]中随机生成x,将[ 1,2,3…x ]排列到array后面组成新数组,直到数组有n位。求对于特定的n有几种不同的数组
思路:
①模拟一下结果就是2n-1 .
②这题要用快速幂 ,不然会超时
题目

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll qpow(ll a,ll b)
{
	ll res=1;
	while(b)
	{
		if(b%2==1)
		{
			res*=a;
			res%=mod;
		}
		b/=2;
		a*=a;
		a%=mod;
	}
	return res;
}
int main()
{
    ll n,t;
    cin>>t;
    while(t--)
    {
    	cin>>n;
    	ll x;
    	x=qpow(2,n-1)%mod;
    	cout<<x<<endl;
	}
    return 0;
}

H - Hsueh- And Treasure

题目
题意: T组数据,每组输入宝藏坐标(x,y),每次从(0,0)出发,第t秒内可以走t个单位,输出到达目标的最短时间和每秒结束后的实时坐标。注意:第t秒内必须精准地走完t个单位,不多不少。
思路: 模拟题。
题目
代码实现大概分这些步骤:
①初始化、处理坐标、特判
②构造函数:先走横坐标
③构造函数:再走纵坐标
④构造函数:处理剩余步数
⑤判断奇偶后处理或转化

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=1e5+5;
int t,walk,dis;
//t为时间  walk为该秒可走步数  dis为剩余步数 
ll xi[maxn],yi[maxn],x,y,fx,fy,xx,yy;
//xi数组和yi数组为实时位置  fx和fy是坐标的正负性 
void remanent()  //处理剩余步数 
{   //如果剩余步数与相差步数同奇偶  可一秒走完
	//否则总是走到距离目标值的一步之距--->转化成同奇偶的情况  
	if((y-yy)%2==1)
	{
		if(dis%2==1)
		{
			t++;
			xi[t]=xx*fx;
			yi[t]=y*fy;
		}
		else
		{
			t++;
			xi[t]=xx*fx;
			yi[t]=(y-1)*fy;
			t++;
			xi[t]=xx*fx;
			yi[t]=y*fy;
		}
	}
	else
	{//dis为奇数可以实现走到一步之距
	 //为偶数可以实现待在原地
		if(dis%2==0)
		{
			t++;
			xi[t]=xx*fx;
			yi[t]=y*fy;
		}
		else
		{
			t++;
			xi[t]=xx*fx;
			yi[t]=(y+1)*fy;
			t++;
			xi[t]=xx*fx;
			yi[t]=(y+1)*fy;
			t++;
			xi[t]=xx*fx;
			yi[t]=y*fy;
		}
	}
} 
void walky()   //再走纵坐标 
{
	if(dis>=y)  //剩余步数>纵坐标 
	{
		remanent();  //处理剩余步数 
		return;
	}
	yy+=dis;
	t++;
	xi[t]=xx*fx;
	yi[t]=yy*fy;
	walk++;
	while(1)
	{
		yy+=walk;
		if(yy>=y)
		{  //纵坐标超过目标值  撤回上一秒的操作 
			dis=walk;  //撤回后剩余步数为walk 
			yy-=walk;  //撤回后纵坐标
			remanent();  //通过绕远路来耗完剩余步数 
			return;
		}
		walk++;  //更新一系列变量 
		t++;
	    xi[t]=xx*fx;
	    yi[t]=yy*fy;
	}
}
void walkx()   //先走横坐标
{
	while(1)
	{
		xx+=walk;
		if(xx>=x)  //走完横坐标
		{
			dis=xx-x;  //这一秒内剩余可走步数
			xx=x;
			walky();  //再走纵坐标 
			return;
		}
		//更新横纵坐标和时间、步数 
		walk++;
		t++;   
		xi[t]=xx*fx;
		yi[t]=yy*fy;
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	for(int tt=1;tt<=T;tt++)
	{
		scanf("%lld%lld",&x,&y);
		printf("Case #%d:\n",tt);
		xx=0,yy=0,t=0,dis=0,walk=1;
		fx=1,fy=1;  //考虑正负号 
	    if(x<0)  fx=-1;
		if(y<0)  fy=-1;
		x=abs(x);
		y=abs(y);
		if(x==0&&y==0)  printf("0\n");  //(0,0)特判
		else
		{
			walkx();  //先走横坐标 
			printf("%d\n",t);
			for(int i=1;i<=t;i++)  
			   printf("%lld %lld\n",xi[i],yi[i]);
		}
	}
	return 0;
} 

I - Iaom and Chicken feet

题目
题意: 存图,找最多有几个有形如上图的“鸡爪”图形,要求每个鸡爪要是不同边集的。
思路: ①先要搞清楚题意,“鸡爪”图形是上面一整个,不是一部分 ②找准合适的节点有规律地分析
题目
题目
观察可知,鸡腿 需要除父节点外的两条边/点爪子 需要除父节点外的三条边/点
∴只需对两部分排列组合后相乘 即可。示例中鸡腿部分为sum=C(deg[2]-1,2),爪子部分为res=C(deg[4],3)
同理,用dfs从根节点开始不断遍历邻居即可。要注意一些特判情况。考虑到数据范围,组合数需要用快速幂再取模。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5;
const ll mod = 998244353;
int n;
ll ans,deg[maxn],s[maxn];;
int cnt, head[maxn];
struct edge
{ 
    int v, next; 
}e[maxn*2];
void add (int u,int v)  //存边 
{
	e[++cnt] = {v, head[u]}; head[u] = cnt;
	e[++cnt] = {u, head[v]}; head[v] = cnt;
}
void init()  //s数组存n的阶乘n! 
{  
	s[0] = 1;
	s[1] = 1;
	for(ll i = 2; i <= 500000; i++)
		s[i] = s[i - 1] * i % mod;
}
ll qpow(ll n,ll k)  //快速幂 
{  
	ll ans=1;
	while(k) 
	{
		if(k&1)
			ans=ans*n%mod;
		n=n*n%mod;
		k/=2;
	}
	return ans;
}
ll C(ll n,ll m) //组合数 
{
	if(n<m) return 0;
	return s[n]*qpow(s[n-m]*s[m]%mod,mod-2)%mod;	
}
void dfs1(int u,int fa)   
{    //从根节点开始遍历相邻的点  记录每个节点入度 
	deg[u]=0;
	for (int i=head[u]; i; i=e[i].next)
	{
		int v=e[i].v;
		if (v==fa) continue;
		dfs1(v, u);
		deg[u]++;
	}
}
void dfs2(int u,int fa) 
{
	ll res=0,sum;   
	//res sum分别负责统计爪子和鸡腿上的情况
	if(fa!=0)  sum=deg[u]+1;
	else  sum=deg[u];
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa)
		{
			if(fa==1&&deg[fa]>=4)
			  res=(res+C(deg[fa]-1,3))%mod;
			if(fa!=1&&deg[fa]>=3)
			  res=(res+C(deg[fa],3))%mod;
		}
		else
		{
			dfs2(v,u);
			if(deg[v]>=3)
			  res=(res+C(deg[v],3))%mod;
		}
	 } 
	 ans=(ans+res*C(sum-1,2))%mod;
}
int main()
{
	scanf("%d",&n);
	init();
	memset(deg,0,sizeof(deg));
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		add(u,v);
	}
	dfs1(1,0);
	dfs2(1,0);
	printf("%lld",ans);
	return 0;
 } 

J - Jew Sorting

题目
题意: 输入k,给出2k 个数,操作:每次平分两段,可舍弃任一部分,直到留下的序列为非降序则结束,求最少需要几次操作
思路: 每次截取2k 的区间,依次遍历查询每个区间,查完再二分重复从头遍历,直到有找到非降序列结束。
题目

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+1e5;
int k,a[maxn];
int main()
{
	scanf("%d",&k);
	int n=1<<k;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int l=1,r=n,num=0,len=n;
	for(int i=l;i<r;i++)
	{
		if(a[i]<=a[i+1])  continue;  //符合条件  能顺利跳出for循环说明是非降序列
		else  //下一组or继续二分
		{
		    if(l+len>n)   //已是当前最后一组 继续二分 
		    {
			    len/=2;
			    num++;
			    l=1;
			    r=l+len-1;
			    i=l-1;
				if(len==1)  break;
		    }
		    else  //跳至下一组区间
		    {
		    	l+=len;
			    r+=len;
			    i=l-1;
			}
		}
	}
	printf("%d",num);
    return 0;
}

也可以用线段树 做(显然我写不出),这里贴一篇很好的科普博客—>传送门
码还是参照铁子哥的(代码作者:Sstee1XD

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 1e5;
int ans = 1;
int a[N], k, n;
struct Seg
{
	int minn[N << 2], maxx[N << 2];
	bool flag[N << 2]; 
	void up(int id, int l, int r)   //向上更新线段树
	{
		if (flag[id << 1 | 1] && flag[id << 1] && maxx[id << 1] <= minn[id << 1 | 1]) 
		{        //即id*2+1
			flag[id] = true;
			minn[id] = minn[id << 1];
			maxx[id] = maxx[id << 1 | 1];
			ans = max(r - l + 1, ans);
		}
	}
	void build(int id, int l, int r)   //建树
	{
		flag[id] = false;
		minn[id] = 0;
		maxx[id] = 0;
		if (l == r) 
		{
			flag[id] = true;
			maxx[id] = a[l];
			minn[id] = a[l];
			return;
		}
		int mid = l + r >> 1;
		build(id << 1, l, mid);
		build(id << 1 | 1, mid + 1, r);
		up(id, l, r);
	}
}seg;
void solve() 
{
	scanf("%d", &k);
	int n = 1 << k;   //即k=2^k
	for (int i = 1; i<= n; ++i)  scanf("%d", &a[i]);
	seg.build(1, 1, n);
	printf("%d\n", k - __lg(ans));
}
int main() {
	int t = 1;
	while(t--) solve();
	return 0;
}

K - K-Clearing II

题目
题目
题意: 输入n m k,n个数,每m个数一组,进行清除操作,统计每组操作结束后有几个数为0,最后累加。注意 ,每次操作结束后,原数组的数据不会改变。
思路: 求每组有几个从k开始的连续的不同的数,再求每个不同的数的个数,最后累加。直接用for循环会超时,要建两棵线段树降低时间复杂度。
①第一棵线段树统计当前区间的[k,k+m]有几个连续不同的数,统计出该区间需要减去的数sum
②第二棵线段树统计各个不同的数的个数,再依次累加
③遍历每棵线段树

没想到吧 根本不会写 哈哈:)

或者用树状数组 (码是铁子哥的,菜鸡还没完全看明白呜呜,来个大佬写个注释吧呜呜)(代码作者:Sstee1XD

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
typedef long long ll;
int n, q, m, k, idk;
int a[N], b[N], id[N];
ll tot[N], had[N];
int num[N];

int lowbit(int x) {  
	return x & -x;
}

void add(int i, ll v, ll bit[]) { 
	while (i <= q) {
		bit[i] += v;
		i += lowbit(i);
	}
}

ll query(int i, ll bit[]) {  
	ll res = 0;
	while (i > 0) {
		res += bit[i];
		i -= lowbit(i);
	}
	return res;
}

void ins(int i) {
	if (!num[i]) {
		add(i, 1ll, had);
	}
	add(i, 1ll, tot);
	num[i]++;
}

void del(int i) {
	num[i]--;
	add(i, -1ll, tot);
	if (!num[i]) {
		add(i, -1ll, had);
	}
	
}

ll gao(int u, int v) {
	if (!num[idk]) return 0;
	ll res = 0;
	int l = idk + 1, r = q, mid;
	while (l <= r) {
		mid = l + r >> 1;
		ll tmp = query(mid, had) - query(idk - 1, had);
		if (tmp < mid - idk + 1) r = mid - 1;
		else l = mid + 1;
	}
	int len = b[l - 1] - b[idk] + 1;
	int pos = lower_bound(b + 1, b + 1 + q, len) - b;
	if (pos > q || b[pos] > len) pos--;
	if (pos <= 0) return 0;
	len = pos;
	return query(len, tot);
}

void solve() {
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		b[i] = a[i];
	}
	sort(b + 1, b + 1 + n);
	q = unique(b + 1, b + 1 + n) - b - 1;  //去重函数  
	//q为去重后尾元素下标   即有几个不同的数 
	int Q = q;
	for (int i = 2; i <= q; ++i) {
		if (b[i] - b[i - 1] > 1) {
			b[++Q] = b[i] - 1;
		}
	}
	q = Q;
	sort(b + 1, b + 1 + q);
	for (int i = 1; i <= n; ++i) {
		id[i] = lower_bound(b + 1, b + 1 + q, a[i]) - b;
		if (a[i] == k) idk = id[i];
	}
	if (!idk) {
		puts("0");
		return;
	}
	ll ans = 0;
	for (int i = 1; i <= m; ++i) {
		ins(id[i]);
	}
	for (int i = 1; i + m - 1 <= n; ++i) {
		if (i != 1) {
			del(id[i - 1]);
			ins(id[i + m - 1]);
		}
		ans += gao(i, i + m - 1);
	}
	printf("%lld\n", ans);
}

int main() {
	int t = 1;
	while(t--) solve();
	return 0;
}

后记

这次省赛选拔赛签到题都没做完要反省。
一个是因为几个人都没有去准备,直接裸考,自己准备的纸质材料也没有,只有几本没看过的算法书。
还有一个是因为确实实力也还没有,其实J挺水,如果静下来好好去想,在赛场上应该还是能写出的,当时几个人状态都不积极觉得会是比较复杂的二分题,还因为觉得写循环必超时就根本没去写 (这边建议要是怕超时以后用个clock()函数—>传送门
希望自己可以这两个月坚持多多刷题,争取六月份个人选拔赛可以留下来呆在实验室。
还要继续加油呐。

;原文链接:https://blog.csdn.net/weixin_50729016/article/details/115380659
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐