2025“钉耙编程”中国大学生算法设计春季联赛(2)

学历史导致的

  • 真心没必要专门写个程序给字符串加引号呀
#include <bits/stdc++.h>
using namespace std;
const string tian[]={"jia","yi","bing","ding","wu","ji","geng","xin","ren","gui"};
const string di[]={"zi","chou","yin","mao","chen","si","wu","wei","shen","you","xu","hai"};
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		string s;
		cin>>s;
		int p=0,q=0;
		int y=1984;
		while(1)
		{
			if(tian[p]+di[q]==s)
			{
				break;
			}
			p=(p+1)%10;
			q=(q+1)%12;
			y++;
		}
		cout<<y<<"\n";
	}
	return 0;
}

学 DP 导致的

  • 提交之前多看两眼呀。直面结果不是急切和无所谓,而是敬畏和自信才对
#include <bits/stdc++.h>
using namespace std;
int f[3005],cnt[30];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		string s,t;
		cin>>s>>t;
		memset(cnt,0,sizeof(cnt));
		for(char c:s)
		{
			cnt[c-'a']++;
		}
		int ans=0;
		for(int i=0;i<26;i++)
		{
			ans=ans+(cnt[i]>0);
		}
		if(t.size()>2)
		{
			cout<<ans<<endl;
			continue;
		}
		int k;
		if(t.size()==2)
		{
			k=10*(t[0]-'0')+(t[1]-'0');
		}
		else
		{
			k=t[0]-'0';
		}
		if(k>=26)
		{
			cout<<ans<<endl;
			continue;
		}
		t="";
		while(k--)
		{
			t+=s;
		}
		ans=0;
		for(int i=0;i<t.size();i++)
		{
			f[i]=1;
			for(int j=0;j<i;j++)
			{
				if(t[j]<t[i])
				{
					f[i]=max(f[i],f[j]+1);
				}
			}
			ans=max(ans,f[i]);
		}
		cout<<ans<<endl;
	}
	return 0;
}

学数数导致的

  • 注意break和continue的作用范围,后面做计算那道题的时候也遇到这个问题
#include <bits/stdc++.h>
using namespace std;
int a[1000005],cnt[1000005],tag[1000005];
vector<int>c[1000005];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			cnt[a[i]]=0;
			c[a[i]].push_back(i);
		}
		int p=0;
		long long ans=0;
		for(int i=1;i<=n;i++)
		{
			if(a[i]&&c[a[i]].size())
			{
				while(p<c[0].size()&&c[0][p]<i)
				{
					p++;
				}
				if(p==c[0].size())
				{
					c[a[i]].clear();
					continue;
				}
				int q=c[0][p];
				int w=upper_bound(c[a[i]].begin(),c[a[i]].end(),q)-c[a[i]].begin();
				if(w==c[a[i]].size())
				{
					c[a[i]].clear();
					continue;
				}
				tag[c[a[i]][w]+1]++;
				c[a[i]].clear();
			}
		}
		c[0].clear();
		tag[n+1]=0;
		long long cur=0;
		for(int i=n;i>=1;i--)
		{
			if(a[i]&&!cnt[a[i]])
			{
				cur++;
			}
			cnt[a[i]]++;
			ans=ans+cur*tag[i];
			tag[i]=0;
		}
		cout<<ans<<endl;
	}
	return 0;
}

学几何导致的

#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		int n,k;
		cin>>n>>k;
		if(k%2==1)
		{
			cout<<0<<endl;
			continue;
		}
		int t=n/k*k+k/2;
		if(n-t<1)
		{
			t-=k;
		}
		int tot=(t+k/2)/k;
		cout<<((n-k/2)+(n-t))*tot/2<<"\n";
	}
	return 0;
}

学博弈论导致的

  • 经典结论:Bash 游戏
  • n 颗石子,每次至多取 m 颗,当且仅当 m+1 | n 时先手必败(后手和先手共同取走 m+1 颗即可)
  • 假设红宝石1元钱,蓝宝石2元钱,宝盒4元钱,那么问题完全等价于:桌上有r+2b+4m元钱,Alice, Bob 轮流取钱,每次可以取 1~3 元,问最后谁胜
  • 认真分析了一会儿,也是很快猜出结论了~
#include <bits/stdc++.h>
using namespace std;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		int r,b,m;
		cin>>r>>b>>m;
		if(b%2==0)
		{
			if(r%4==0)
			{
				cout<<"Bob\n";
			}
			else
			{
				cout<<"Alice\n";
			}
		}
		else
		{
			if(r%4==2)
			{
				cout<<"Bob\n";
			}
			else
			{
				cout<<"Alice\n";
			}
		}
	}
	return 0;
}

学画画导致的

  • 其实这道题是一道阅读理解题。你以为你读懂题目了,其实你只是读懂了题目的形式,而没有理解题目的本质。而当你沉下心来,再读一遍题目之后,做法自然水落石出
#include <bits/stdc++.h>
using namespace std;
struct t1
{
	int x,y,c;
}t[300005];
vector<int>a[900005];
int d[900005],tot;
void topsort(int n)
{
	queue<int>q;
	for(int i=1;i<=3*n;i++)
	{
		if(d[i]==0)
		{
			q.push(i);
		}
	}
	while(q.size())
	{
		int n1=q.front();
		q.pop();
		tot++;
		for(int i=0;i<a[n1].size();i++)
		{
			
			d[a[n1][i]]--;
			if(d[a[n1][i]]==0)
			{
				q.push(a[n1][i]);
			}
		}
	}
}
void add(int u,int v)
{
	if(u!=v)
	{
		a[u].push_back(v);
		d[v]++;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		int n,m;
		cin>>n>>m;
		for(int i=1;i<=3*n;i++)
		{
			a[i].clear();
			d[i]=0;
		}
		tot=0;
		for(int i=1;i<=m;i++)
		{
			cin>>t[i].x>>t[i].y>>t[i].c;
		}
		bool ans=true;
		for(int i=1;i<=m;i++)
		{
			int x=(t[i].y+1)/2;
			int y=2*n+1-t[i].x;
			int z=2*n+t[i].x-t[i].y/2;
			if(x!=t[i].c&&y!=t[i].c&&z!=t[i].c)
			{
				ans=false;
				break;
			}
			else
			{
				add(t[i].c,x);
				add(t[i].c,y);
				add(t[i].c,z);
			}
		}
		if(ans==false)
		{
			cout<<"No\n";
			continue;
		}
		topsort(n);
		if(tot==3*n)
		{
			cout<<"Yes\n";
		}
		else
		{
			cout<<"No\n";
		}
	}
	return 0;
}

其实下面的三道题都在你的能力范围内,如果专心做其中任何一道,我想你都能做出来。可是在赛场上,同时面对这三道题目,“乱花渐欲迷人眼”,你难免优柔寡断,担心选错方向而不敢全力以赴。


学排列导致的

  • 赛场上也想过用线段树维护,但很快就自我否定了。为什么呢?对每个线段树节点维护1个大小为30的数组,空间已经吃紧了吧,开30个怎么可能开得下呢?可是,由于你要存的数都在30以内,完全可以用char类型存,然后再用动态开点线段树的方法省掉一倍空间,算一下就会发现,\(200000*30*30/1024/1024*2=343.3MB\),完全开得下呀!
  • 注意到\(\circ\)有结合律,因此直接用线段树维护
  • 直接求逆排列的思维太反直觉了,顺应自己的直觉,用逆元的方法求逆排列,记\(s_i=p_1\circ p_2\circ ...\circ p_i\),然后有\(s_{l-1}\circ p_l\circ p_{l+1} \circ ...\circ p_r=s_r\),于是\(p_l\circ p_{l+1} \circ ...\circ p_r=(s_{l-1})^{-1}\circ s_r\)
  • 给定\(1\sim n\)的一个排列和它的每个元素的排名的序列,就互为逆排列
  • 本题有大量置换,尝试把它封装成一个模块以简化编程复杂度:事先写一个结构体perm,重载u*v运算并实现求逆元
  • 注意标记为0时不能下传。虽然对修改没有影响,但是会覆盖原来的标记。怀揣找出错误的渴望,很快就能发现程序的问题所在呢
#include <bits/stdc++.h>
using namespace std;
unsigned long long Seed;
unsigned myrand(){Seed^=Seed>>5;Seed^=Seed<<3;return Seed;}
//template< typename T >void swap(T u,T v){T t=u;u=v;v=t;} //如选手未调用 algorithm 库,请取消注释本行
template< typename T >void generate(int k,T p[]){//k 为题目中的 k,p[] 为产生的排列存储位置,下标从 1 开始
    auto *q=p+1;
    for(int i=1;i<=k;i++)p[i]=i;
    for(int i=k-1;i>0;i--)swap(q[i],q[myrand()%i]);
}
int n,m,k;
struct perm
{
	char p[30+1];
	perm()
	{
		iota(p+1,p+k+1,1);
	}
	perm inv()
	{
		perm res;
		for(int i=1;i<=k;i++)
		{
			res.p[p[i]]=i;
		}
		return res;
	}
};
perm operator *(perm a,perm b)
{
	perm c;
	for(int i=1;i<=k;i++)
	{
		c.p[i]=a.p[b.p[i]];
	}
	return c;
}
struct t1
{
	int l,r,bj;
	perm k[30+1];
}t[400005];
int tot;
int build(int p,int l,int r)
{
	if(!p)
	{
		p=++tot;
		t[p].bj=0;
	}
	if(l==r)
	{
		generate(k,t[p].k[0].p);
		for(int i=1;i<=k;i++)
		{
			for(int j=1;j<=k;j++)
			{
				t[p].k[t[p].k[0].p[i]].p[j]=t[p].k[0].p[(i+j-2)%k+1];
			}
		}
		return p;
	}
	int mid=(l+r)>>1;
	t[p].l=build(0,l,mid);
	t[p].r=build(0,mid+1,r);
	t[p].k[0]=t[t[p].l].k[0]*t[t[p].r].k[0];
	for(int i=1;i<=k;i++)
	{
		t[p].k[i]=t[t[p].l].k[i]*t[t[p].r].k[i];		
	}
	return p;
}
void spread(int p)
{
	if(t[p].bj)
	{
		t[t[p].l].bj=t[p].bj;
		t[t[p].r].bj=t[p].bj;
		t[t[p].l].k[0]=t[t[p].l].k[t[p].bj];
		t[t[p].r].k[0]=t[t[p].r].k[t[p].bj];
		t[p].bj=0;
	}
}
perm ask(int p,int l,int r,int u,int v)
{
	if(u>v)
	{
		return *new perm;
	}
	if(u<=l&&v>=r)
	{
		return t[p].k[0];
	}
	spread(p);
	int mid=(l+r)>>1;
	perm res;
	if(u<=mid)
	{
		res=res*ask(t[p].l,l,mid,u,v);
	}
	if(v>mid)
	{
		res=res*ask(t[p].r,mid+1,r,u,v);
	}
	return res;
}
void change(int p,int l,int r,int u,int v,int z)
{
	if(u<=l&&v>=r)
	{
		t[p].k[0]=t[p].k[z];
		t[p].bj=z;
		return;
	}
	spread(p);
	int mid=(l+r)>>1;
	if(u<=mid)
	{
		change(t[p].l,l,mid,u,v,z);
	}
	if(v>mid)
	{
		change(t[p].r,mid+1,r,u,v,z);
	}
	t[p].k[0]=t[t[p].l].k[0]*t[t[p].r].k[0];
}
void output(perm x)
{
	for(int i=1;i<=k;i++)
	{
		cout<<(int)x.p[i]<<" ";
	}
	cout<<endl;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m>>k>>Seed;
	build(0,1,n);
	for(int i=1;i<=m;i++)
	{
		int op,l,r,z;
		cin>>op>>l>>r>>z;
		if(op==1)
		{
			perm ans=ask(1,1,n,1,l-1).inv()*ask(1,1,n,1,r);
			cout<<(int)ans.p[z]<<"\n";
		}
		else
		{
			change(1,1,n,l,r,z);
		}
	}
	return 0;
}

学位运算导致的

  • 1e16的本质是科学计数法,而科学计数法的本质是浮点数,用在1e16这样的大整数上会导致精度误差【之前还奇怪为什么1e运算扩展到long long范围时不用加标识符呢】
  • 其实这道题你几乎都想出正解了呀,可还是自我否定了,为什么呢?因为用到了最“值”的方法,之前做某道位运算题的时候你也用到了最“值”,事后证明是错解。但这道题给了你一个完备集,正解就是求最“值”呀。包括上一题也是。由此可见,你之前认为是错误的很多想法其实并非毫无价值,只是没有出现在正确的位置。因此,你不应该囿于过往的经验,而应该在经验的基础上理性分析
  • \(a\& b \le min(a,b)\) \(a|b \ge max(a,b)\)
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
int a[500005];
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		int n,q;
		cin>>n>>q;
		int s[64]={0};
		fill(s,s+64,0ull-1);
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			for(int j=0;j<64;j++)
			{
				if((a[i]>>j)&1)
				{
					s[j]=s[j]&a[i];
				}
			}
		}
		int res=0;
		for(int i=1;i<=q;i++)
		{
			int x;
			cin>>x;
			int ans=0ull-1,cur=0;
			for(signed j=63;j>=0;j--)
			{
				if(((x>>j)&1)==0)
				{
					ans=min(ans,cur|s[j]);
				}
				else
				{
					cur=cur|s[j];
				}
			}
			ans=min(ans,cur);
			res=(res^(ans%10000000000001029ll));
		}
		cout<<res<<"\n";
	}
	return 0;
}

学计算导致的

  • 虽然你看到这个题就觉得是DP,题解的标签也是DP,从本质上讲似乎也可以说它是DP,但由于DP在你的刻板印象里与倒推的思维方式绑定,因此对你而言,还是用【记忆化搜索遍历状态空间】概括这道题的解法更加妥帖
  • 用sum, mul, cur分别表示已经结束的所有项之和、当前项的乘数, 以及当前要计算的数字
  • 也可以根据数据范围推一下做法,特别是数据小的时候。(nm)^2 很难再加一个 k,nmk^2 还有空余,所以很可能是 nmk^3
  • 状态压缩可以这样写:
int enc(int sum,int mul,int cur)
{
	return (sum*k+mul)*k+cur;
}
  • ASCII码最大到127,可以通过预处理出每个状态的后继状态构建自动机,既降低编程复杂度也优化常数
  • 用BFS实现提交一直超时,不知何故;注意在 C++17 之前,如果要直接创建结构体变量,就必须给出构造函数
#include <bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int n,m,k;
char c[101][101];
int f[101][101][8000];
int tr[128][8000];
int enc(int sum,int mul,int cur)
{
	return (sum*k+mul)*k+cur;
}
void add(int &x,int y)
{
	x+=y;
	if(x>=mod)
	{
		x-=mod;
	}
}
void dp()
{
	f[1][1][enc(0,1,(c[1][1]-'0')%k)]=1;
	for(int x=1;x<=n;x++)
	{
		for(int y=1;y<=m;y++)
		{
			for(int s=0;s<k*k*k;s++)
			{
				int val=f[x][y][s];
				if(x+1<=n)
				{
					char ch=c[x+1][y];
					int u=x+1,v=y,t=tr[ch][s];
					if(isdigit(c[x][y])||isdigit(ch))
					{
						add(f[u][v][t],val);
					}	
				}
				if(y+1<=m)
				{
					char ch=c[x][y+1];
					int u=x,v=y+1,t=tr[ch][s];
					if(isdigit(c[x][y])||isdigit(ch))
					{
						add(f[u][v][t],val);
					}
				}
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		memset(f,0,sizeof(f));
		cin>>n>>m>>k;
		for(int sum=0;sum<k;sum++)
		{
			for(int mul=0;mul<k;mul++)
			{
				for(int cur=0;cur<k;cur++)
				{
					for(int ch='0';ch<='9';ch++)
					{
						tr[ch][enc(sum,mul,cur)]=enc(sum,mul,(cur*10+ch-'0')%k);
					}
					tr['+'][enc(sum,mul,cur)]=enc((sum+mul*cur)%k,1,0);
					tr['-'][enc(sum,mul,cur)]=enc((sum+mul*cur)%k,k-1,0);
					tr['*'][enc(sum,mul,cur)]=enc(sum,mul*cur%k,0);
				}
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				cin>>c[i][j];
			}
		}
		dp();
		int ans=0;
		for(int sum=0;sum<k;sum++)
		{
			for(int mul=0;mul<k;mul++)
			{
				for(int cur=0;cur<k;cur++)
				{
					if((sum+mul*cur)%k==0)
					{
						add(ans,f[n][m][enc(sum,mul,cur)]);
					}
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2025-03-18 22:32  D06  阅读(103)  评论(0)    收藏  举报
//雪花飘落效果