25杭电春季第二场

1002

给出干支纪年法,确定是哪一年?只要求一个周期里的年份,也就是60年内,直接打表先确定每个干支纪年法对应的年份,然后查表

#include<bits/stdc++.h>
using namespace std;
vector<string>va={"jia", "yi", "bing", "ding", "wu", "ji", "geng", "xin", "ren", "gui"};
vector<string>vb={"zi", "chou", "yin", "mao", "chen", "si", "wu", "wei", "shen", "you", "xu", "hai"};
map<string,int>mp;
void solve(){
	string s;
	cin>>s;
	cout<<mp[s]<<'\n';
}
int main(){
	int t;
	cin>>t;
	int x=0,y=0;
	for(int i=1984;i<=2043;i++){
		string s=va[x]+vb[y];
		mp[s]=i;
		x++;
		y++;
		x%=10;
		y%=12;
	}
	while(t--){
		solve();
	}
}

1003

计数,问子序列 ( p , 0 , p , q ) (p,0,p,q) (p,0,p,q)出现多少次?

q q q只出现一次,可以枚举 q q q,那么对于每个 q q q,对答案的贡献就是他左侧 ( p , 0 , p ) (p,0,p) (p,0,p)的个数。 ( p , 0 , p ) (p,0,p) (p,0,p)可以先预处理,具体来说检查一个 p p p能不能构成 ( p , 0 , p ) (p,0,p) (p,0,p)可以检查它左侧是否存在另一个 p p p,并且中间有至少一个 0 0 0,那贪心的想,上一个 p p p肯定要取最左侧的,这样中间的范围尽可能大,出现 0 0 0的可能更大,因此这需要我们记录每个元素第一次出现的位置。

另外需要注意,要求的是满足条件的不同的 ( p , q ) (p,q) (p,q)对数,因此一个 q q q只能被计算一次,那么贪心的想,应该取尽可能靠右的 q q q,左侧出现 ( p , 0 , p ) (p,0,p) (p,0,p)的可能更大。

void solve(){
	int n;
	cin>>n;
	vector<int>a(n+1),sum(n+1);
	int mx=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+(a[i]==0);
		mx=max(mx,a[i]);
	}
	vector<int>fi(mx+1),lst(mx+1);
	for(int i=1;i<=n;i++){
		if(fi[a[i]]==0){
			fi[a[i]]=i;
		}
		lst[a[i]]=i;
	}
	vector<int>vis(mx+1);
	int tot=0;
	int ans=0;
	for(int i=1;i<=n;i++){
		if(i==lst[a[i]]&&a[i]){
			ans+=tot;
		}
		if(fi[a[i]]&&a[i]){
			if(sum[i-1]-sum[fi[a[i]]]>0){
				if(!vis[a[i]]){
					vis[a[i]]=1;
					tot++;
				}
			}
		}
	}
	cout<<ans<<'\n';
}

1004

思维+dp

一个只含小写字母的字符串,拼接最多 k = 1 0 100 k=10^{100} k=10100次,问 L I S LIS LIS的长度?

注意到字符集很小,也就是元素种类数很小,并且是严格递增的 L I S LIS LIS,因此最大长度显然只有 26 26 26,构造方式是第一个周期取 a a a,第二个周期取 b b b,以此类推。因此拼接次数很大,并不需要真的去 d p dp dp,可以直接输出答案。

具体来说,字符串里不同元素个数是 n n n的话, k > = n k>=n k>=n时答案就是 n n n k < n k<n k<n时可以直接 d p dp dp,由于 k k k不大复杂度不会太高

需要注意的是 k k k很大,直接读取会爆,可以用字符串读取然后判断长度。

void solve(){
	int n,k;
	string s,ss;
	cin>>s>>ss;
	n=s.size();
	if(ss.size()>10){
		k=1000;
	}
	else{
		k=stoi(ss);
	}
	
	set<char>st;
	for(int i=1;i<=n;i++){
		st.insert(s[i-1]);
	}
	if(k>=st.size()){
		cout<<st.size()<<'\n';
	}
	else{
		string t;
		for(int i=0;i<k;i++){
			t+=s;
		}
		vector<int>mx(26);
		int ans=0;
		for(int i=1;i<=n*k;i++){
			int pre=0;
			int x=t[i-1]-'a';
			for(int j=0;j<x;j++){
				pre=max(pre,mx[j]);
			}
			mx[x]=max(mx[x],pre+1);
			ans=max(ans,mx[x]);
		}
		cout<<ans<<'\n';
	}
}

1005

伪装成计算几何的数学(很多计算几何都是别的知识点伪装的)

给一个直线,绕固定点转 n = 1 e 9 n=1e9 n=1e9次,每次转 180 / k 180/k 180/k度,问有多少对直线垂直?

首先显然的是,由于垂直是把 180 180 180度对半分了,只有 k k k是偶数才能对半分,奇数直接无解。

其次,可能出现循环,转一圈就转回来了,因此最终,每一个度数的直线条数,都是类似于整除,带几个余数的样子的,这是个经典模式,也就是首先 y y y个元素都是 t = x / y t=x/y t=x/y,然后余数是 r r r的话,前 r r r个可以加一,变成 x / y + 1 = t + 1 x/y+1=t+1 x/y+1=t+1.

此时我们知道每个角度的元素个数了,然后就是个计数问题,一个度数 a a a,只能和 a − 90 a-90 a90配对,那么对答案的贡献就是 c n t [ a ] ∗ c n t [ a − 90 ] cnt[a]*cnt[a-90] cnt[a]cnt[a90]。由于 c n t cnt cnt的值实际上最多两种 ( t , t + 1 ) (t,t+1) (t,t+1),我们只要知道这两种值的个数就可以可以 O ( 1 ) O(1) O(1)计算了

这里分类讨论一下,如果余数 r > n / 2 r>n/2 r>n/2,那么有一部分是 ( t + 1 ) ∗ ( t + 1 ) (t+1)*(t+1) (t+1)(t+1),剩下是 ( t + 1 ) ∗ t (t+1)*t (t+1)t。如果 r < = n / 2 r<=n/2 r<=n/2,那么一部分是 ( t + 1 ) ∗ t (t+1)*t (t+1)t,一部分是 t ∗ t t*t tt

void solve(){
	int n,k;
	cin>>n>>k;
	int ans=0,ans1=0;
	if(k%2){
		ans=0;
	}
	else{
		int r=n%k;
		int x=n/k;
		if(r>=k/2){
			ans+=(r-k/2)*(x+1)*(x+1);
			ans+=(k-r)*(x+1)*x;
		}
		else{
			ans+=r*(x+1)*x;
			ans+=(k/2-r)*x*x;
		}
	}
	cout<<ans<<'\n';

}

1006

博弈sg打表/结论

有一坨转移,如图
在这里插入图片描述
有直接分析的方法,可以把三种物品都变成第一种,也就是有 a + 2 b + 4 c a+2b+4c a+2b+4c个第一种物品,然后每次可以拿 0 − 3 0-3 03个,这是经典 n i m nim nim游戏。模 4 4 4 0 0 0的话,后手可以控制自己拿的个数,让每一轮自己拿的+对手拿的=4,这样最后可以让先手没得拿,也就是先手必败。否则,先手可以拿几个,变成模 4 4 4 0 0 0留给后手,后手必败,先手必胜。

但是看不出转移,也可以打表。一种方式是直接三重循环枚举所有 a b c abc abc的组合,输出是必败还是必胜,但这样没那么好观察。关键在于这里是三个变量的,三重循环打表,同一时间只有一个变量在变化,不好看出三种变量的总的关系。对于这种多个变量的博弈,一种比较好观察的打表是,把三个变量压缩成一个整数,给每个变量安排一个 b a s e p base^p basep,由于 b a s e i base^i basei之间是互相独立的,不同的 a b c abc abc压缩后可以对应不同的值。

本题就可以 z = a + 2 b + 4 c z=a+2b+4c z=a+2b+4c的方式压缩,这样压缩完就能看出来模 4 4 4 0 0 0是必败,其余都是必胜。这个压缩方式和第一种思路的结论是一样的。

这题的两个教训是:
多个变量的打表可以压缩,就算不压缩,也应该多重循环打表,否则可能有遗漏;

打表的时候可以猜想,然后验证,但是猜想只是猜想,不要认定一个猜想就是对的,如果发现不对,应该迅速换一个猜想继续验证,不要死磕。并且选择猜想的时候,应该是基于基础打表(比如多重循环)来观察的,观察到某些规律,再来验证,而不是随便想一个猜想就开始验证。

这题赛时完全可以观察出来的,但是我随便选择了一个猜想,然后就掉进去了,后面一直在思考这个猜想,并没有及时更换思路。并且我这个猜想也是随便想的,并没有三重循环打表,然后观察,实际上错得离谱,是无意义的猜想。

void solve(){
	int a,b,c;
	cin>>a>>b>>c;
	int x=a+2*b+4*c;
	if(x%4){
		cout<<"Alice\n";
	}
	else{
		cout<<"Bob\n";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值