GDOI2022PJ题解

D1T1 邹忌讽齐王纳谏

题目

齐国人邹忌对齐国国君齐威王说,大王身边的人会因为私情、利益等原因而对大王阿谀奉承,所以不能光听好话,只有广泛接受群众的批评意见,才不会被蒙蔽双眼,齐国才能强盛。齐威王接受了这个意见,于是昭告全国:

  1. 如果有臣民当面对齐威王提出建议,则获得价值为 A 的奖励;
  2. 如果有臣民以书信的方式对齐威王提出建议,则获得价值为 B 的奖励;
  3. 如果有臣民在街市中议论齐威王,意见流传到宫廷,则获得价值为 C 的奖励。

你通过史书整理出了某一年的建议记录,按时间顺序罗列,共有 n 条,每条记录形如”name way”,其中 name 是由小写英文字母组成的字符串(由于博物馆的计算机太古老了,只支持英文,所以你也只能用英文做记录),表示提建议者的名字;way 是一个为 1、2 或 3 的数字,表示提建议的方式。

你发现记录中有些人提出了多次建议,从而获得多次奖励,因此你很好奇这些人之中获得奖励总和最多的是谁,他总共获得了多少奖励。

如果获得最多奖励的不止一个人,请输出最早获得最多奖励的人。

思路

语法题。

按照题意所说模拟,把每个人的名字转成数字然后放到一个桶里面(或者直接试string+map也行)。

桶记录的是每个人的贡献,动态维护最大值即可。

注意考虑全0情况。(不过数据好像没卡)

Code

#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int f=1,x=0;char ch=getchar();while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, T, x[5], mx=-1, mi;
string s, c; 
map<string, int>mp; 

signed main()
{
	
	cin>>n>>x[1]>>x[2]>>x[3]; 
	for(i=1; i<=n; ++i)
	{
		cin>>s>>j; 
		mp[s]+=x[j]; 
		if(mp[s]>mx) mx=mp[s], c=s; 
	}
	cout<<c<<" "<<mx; 
	return 0;
}

总结

这是一道挺语法的题目。

数据没有卡全0的情况,但有时在做这种题的时候需要考虑。

对于最大值的初始值,可以默认赋为-inf,也可以去考虑最大值最小为多少。

D1T2 数列游戏

题目

有一个长度为 \(n\) 的序列 \(a_1, \dots , a_n\)

如果序列的长度大于 1,那么你就能进行操作,每次操作可以选择两个相邻的数 \(a_i, ai+1\) 合并,得到一个新的数 \(a_i\)\(a_{i+1}\)(“⊕”表示异或),每次操作都会使序列的长度减少 1。例如对将序列 \([8, 3, 5, 7, 1]\) 中的第 2个和第 3 个数进行合并,会得到新序列 \([8, 6, 7, 1]\),并可以进行下一轮操作。

你需要进行若干次操作(可能是 0 次),使得最终序列任意子区间的异或和不为 0。子区间的定义为连续的一段数 $ [a_l, a_{l+1}, \dots ,a_r](l \leqslant r)$。

求满足条件的最终序列的最长长度。

思路

记数列 \(a\) 的异或前缀和为 \(s\),则区间 \([l, r]\) 的异或和为 \(s_r\)\(s_{l-1}\)

我们要使任意区间异或和不为0,就是要是 \(s\) 互不相同且皆大于0.

因此,我们要将不符合要求的 \(s\) 消除。

假如我们想要让某个 \(s_i\) 消除,我们只需要合并 \(i\)\(i+1\) 即可。

但在程序实现的过程中,我们并不需要模拟消除,只需要统计多少个不同的即可。

无解的情况是整个数列异或和为0。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline 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<<1)+(x
<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, T;
map<int, int>mp; 

signed main()
{
//	freopen("tiaoshi.in", "r", stdin);
//	freopen("tiaoshi.out", "w", stdout);
	n=read(); mp[0]=1; 
	for(i=1; i<=n; ++i)
	{
		j=read(); k^=j; 
		if(!mp[k]) ++m, mp[k]=1; 
	}
	printf("%lld", (k==0 ? -1 : m)); 
	return 0;
}

总结

这道题在考场上没想出来,值得反思。

对于区间异或和的问题,可以多思考前缀异或和的思想。

而区间异或和为0的情况,就是存在前缀异或和相等。

以后对于区间异或的情况可以多往这个方面想。

D1T3 流水线

题目

在计算机组成原理这门课中,小明的老师布置了实现 CPU 流水线的作业。小明打算设计出一个效率最高的流水线。简单来说,流水线就是将 CPU 分成若干个任务模块,而一个模块又可以继续划分成更小的模块,小模块可以划分成更小的小小模块......根据常识我们知道把一个任务划分后,每一个部分的代价会变少,但是可能会产生额外的代价。所以小明希望你帮助他解决这个问题。

我们可以用一棵以 1 为根的有根树来描述模块之间的关系,每个节点是一个模块,每个节点的点权对应着该模块的时间代价。每一个非叶子节点可以划分成该节点的儿子节点对应的模块。

每个模块都有一定的时间代价,而流水线最后的效率我们可以用划分的模块数乘上模块中时间代价最大的一个来表示,时间代价越小,流水线的效率越高。也就是说,假如小明最后把 CPU 划分为了 \(m\) 个模块,每个模块的代价为 \(w_1, w_2, \dots, w_m\),则总代价为 \(m \times \max(w_1, w_2, \dots, w_m)\)。另外,我们认为根节点对应的模块不往下划分模块也是一种合法的方案。

请你帮小明找到效率最高的流水线设计方案吧

思路

假设我们的枚举方案顺序是从根接待你开始自上而下。

我们现在有一种方案,其最大值为 \(x\),模块个数为 \(y\)

我们现在要向下走,把某个点变成其儿子节点。

在这过程中,显然, \(m\) 一定不会变小

那么要使结果反而变小,我们就要从 \(\max(w_1, w_2, \dots, w_m)\) 入手,使这个变小。

我们把当前的最大值 \(w_i\) 拿出来,变成其儿子节点,这样就会使 \(\max(w_1, w_2, \dots, w_m)\) 有机会变小,才能使结果反而小。

整个过程我们可以从根节点开始,用优先队列维护最大值即可。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int f=1,x=0;char ch=getchar();while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 200010
//#define M
//#define mo
struct node
{
	int x, y; 
	bool operator <(const node &A) const
	{
		return y<A.y; 
	}
}p; 
int n, m, i, j, k, T;
vector<int>v[N]; 
int w[N], f[N], c[N], ans; 
priority_queue<node>q; 
//priority_queue<node>q; 

signed main()
{
	
	n=read(); 
	for(i=1; i<=n; ++i) w[i]=read(); 
	for(i=2; i<=n; ++i) 
	{
		f[i]=read(); c[f[i]]=1; 
//		printf("%lld\n", f[i]); 
		v[f[i]].push_back(i); 
	} 
	p.x=1; p.y=w[1]; q.push(p); ans=w[1]; 
	while(!q.empty())
	{
		p=q.top(); q.pop(); 
//		printf("%lld\n", p.x); 
		if(!c[k=p.x]) return printf("%lld\n", ans), 0; 
		for(i=0; i<v[k].size(); ++i) 
		{
			p.x=v[k][i]; p.y=w[v[k][i]]; 
			q.push(p); 
		}
		ans=min(ans, (int)q.top().y*(int)q.size() ); 
	}
	return 0;
}

总结

这道题是一道挺妙的思维题,题目的关键条件在于 \(w_i\leqslant w_{f_i}\)

抓住这个关键条件,我们可以发现答案的两个变量的变化关系,然后用优先队列维护。

对于这类式子含最大最小值的题目,可以考虑优先队列。

对于式子里有多个影响因素,可以考虑每个影响因素的变化。

同时也要注意观察题目不起眼的特殊条件。

D1T4 小学生计数题

题目

作为 GDOI 的组题人,小 Y 需要整理手中已有的题目,考虑它们的难度以及所考察的知识点,然后将它们组成数套题目。

小 Y 希望先能组出第一套题目,为了整套题目具有良好的区分度,在一套题目中:

  • 所有题目的难度需要能排成等差数列;(也就是说,若将所有题目按难度从小到大排序,那么每相邻两题的难度的差相等,这个差叫做公差)
  • 每道题目的难度都是公差的倍数,公差不为 0;
  • 需要有不少于 \(L\) 道题,不多于 \(R\) 道题。

现在小 Y 手里已经有了 \(m\) 道题目,其中难度为 \(a_i\) 的题有 \(c_i\)\((1 ≤ i ≤ n)\)

小 Y 希望能够知道,他有多少种不同的方式能够组出一套题目。

在这道题目中,我们认为两种组题方式不同当且仅当 \(∃k(1 ≤ k ≤ m)\),使得一种方案包含第 \(k\) 道题而另一种方案不包含。

由于答案可能很大,输出答案对 \(998244353\) 取模。

思路

先不考虑题目数量,我们把所有等差数列抽出来。满足这些等差数列都不满足一个完全包含另一个。

我们把这种等差数列定义为特殊等差数列

那么此时对于任意一种难度 \(a_i\),它最多会被多少个特殊等差数列包含?

显然,对于所有特殊等差数列,其公差必然互不相同

于是,这种难度 \(a_i\),它的因数个数必然小于100个(打个程序可以验证),所以包含它的特殊等差数列个数必然小于100个。

然后我们就可以把所有特殊等差数列处理出来了。上面的时间复杂度是 \(O(n\log n)\)

我们把这些抽出来的等差数列分别处理,看看其有多少个符合要求的等差数列。

暴力的思想使枚举开头和结尾,然后再暴力循环一遍求其方案,复杂度为 \(O(n^3)\),总复杂度为 \(O(n^4\log n)\)

显然,最后一重暴力循环不必要,我们可以求个乘积前缀和,然后套费马小定理求逆元再求乘积,复杂度 \(O(n^3\log n)\)

然后我们发现逆元也可以再做一个前缀和,然后再省掉一重循环,变为 \(O(n^2\log n\log n)\),还有一个 \(\log\) 是因为求逆元有个快速幂需要 \(\log\)。(我不知道线性求逆元在此题可不可行,感兴趣可以试一试)

时间复杂度看起来会爆,然而,每个特殊等差数列。假如其个数多,那么长度小。假如个数小,长度多。因此均摊下来是 \(O(n\log n\log n)\) 的。

官方数据似乎不需要逆元前缀和的优化,但民间数据卡了。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int f=1,x=0;char ch=getchar();while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 800010
//#define M
#define mo 998244353
struct node
{
	int x, y; 
}ax[N]; 
int n, m, i, j, k, T; 
int a[N], b[N], c[N], d[N], x[N], s[N], mp[N]; 
int ans, L, R, sx[N], sy[N]; 
map<int, int>dp[N]; 
int sum, anss;  

bool cmp(node x, node y)
{
	return x.x<y.x; 
}

int kuai(int a, int b)
{
	int ans=1; 
	while(b)
	{
		if(b&1) ans=ans*a%mo; 
		a=a*a%mo; 
		b>>=1; 
	}
	return ans; 
}

//int chu(int a, int b)
//{
//	return a*kuai(b, mo-2)%mo; 
//}

//int pan(int l, int r, int k)
//{
//	int i, j, ans=0; 
//	for(i=1; i<=r; ++i)
//		for(j=i-k+1; j<=i; ++j) 
//		{
//			ans+=chu(s[i], s[j-1]); 
//			ans=(ans+mo)%mo; 
//		}
//	return ans; 
//}

int panp(int n, int l, int r)
{
	int  i, j, ans=0; 
	for(i=l; i<=n; ++i)
	{
		sum=(sy[i-l]-sy[max(1ll, i-r+1)-2])%mo; 
		ans+=s[i]*sum%mo; 
		ans=(ans+mo)%mo;  
	}
		
	return ans; 
}

void check(int i, int k)
{
	int m=0, j; 
	while(i) d[++m]=i, i=dp[i][k]; 
	for(i=1, j=m; i<=m; ++i, --j) x[i]=d[j]; 
//	for(i=1; i<=m; ++i) printf("%lld ", a[x[i]]); 
//	printf(":%lld\n", k); 
	for(i=1; i<=m; ++i) d[i]=c[x[i]]; 
	for(i=s[0]=1; i<=m; ++i) s[i]=s[i-1]*d[i]%mo; 
	for(i=0; i<=m; ++i) sx[i]=kuai(s[i], mo-2)%mo; 
	for(i=0; i<=m; ++i) sy[i]=((i-1==-1 ? 0 : sy[i-1])+sx[i])%mo; 
//	for(i=1; i<=m; ++i) printf("%lld ", s[i]); 
//	printf("\n"); 
//	ans+=(pan(1, m, R)-pan(1, m, L-1)); 
	ans+=panp(m, L, R); 
	ans=(ans%mo+mo)%mo; 
}

signed main()
{
//	freopen("counting.in", "r", stdin);
//	freopen("counting.out", "w", stdout);
//	freopen("tiaoshi.in", "r", stdin);
//	freopen("tiaoshi.out", "w", stdout);
	n=read(); m=read(); L=read(); R=read(); 
	for(i=1; i<=n; ++i) 
		ax[i].x=read(), ax[i].y=read(); 
	sort(ax+1, ax+n+1, cmp); 
	for(i=1; i<=n; ++i)
		a[i]=ax[i].x, c[i]=ax[i].y; 
//	for(i=1; i<=n; ++i) printf("%lld ", a[i]); 
	for(i=1; i<=n; ++i) mp[a[i]]=i; 
	for(i=1; i<=n; ++i)
	{
//		if(a[i]==0) continue; 
		m=0; 
		if(a[i]==0) 
		{
			for(j=2; j<=n; ++j) 
				dp[j][a[j]]=1; 
			continue; 
		}
		for(j=1; j*j<=a[i]; ++j)
			if(a[i]%j==0)
			{
				b[++m]=j; b[++m]=a[i]/j; 
				if(j*j==a[i]) --m; 
			} 
		sort(b+1, b+m+1); 
//		printf("%lld:", a[i]); 
		for(j=1; j<=m; ++j)
		{
//			printf("%lld ", b[j]); 
			if(k=mp[a[i]+b[j]]) dp[k][b[j]]=i; 
			else check(i, b[j]); 
		}
//		printf("\n"); 
	}
	printf("%lld", ans); 
	return 0;
}



4.25更新:此题需要特判一下 \(a_i=0\) 的情况(官方数据没卡,但万恶的民间出题人卡了)

总结

首先对于这道题,首先要想到的是制造特殊等差数列,因为很多情况就可以归到一起来考虑了。

然后是对于题目中等差数列公差的特殊定义可以去思考,然后发现特殊等差数列个数受限于数的因数个数。

然后后面的优化就是凭经验了。对于多次求逆元,可以考虑逆元前缀和进行优化。

D2T1 点指兵兵

题目

你一定有过在两个物品之间犹豫不决的时候,想要借助一些方法帮你随机选择。

在广东,有一种方法叫”点指兵兵”,即一开始用手指指向一个物品,然后念” 点指兵兵点到谁人做大兵”,从第二个字开始,每念一个字,手指就移动到另一个物品上。

整句话念完,手指指向谁,你就选择谁。

但很快你就发现,这个方法是有问题的——你最终指向的物品一定是你一开始指向的物品,这严重破坏了这个方法的随机性。

后来你学了信息学,你更加清楚这个规律了,咒语长度是奇数的时候最终指向跟初始指向相同,咒语长度是偶数的时候最终指向跟初始指向不同。

所以解决的方法有这么几种:

  • 增加咒语长度,可以念一句很长很长的句子,长到你也数不清是奇数还是偶数。
  • 增加物品数量,只有两个物品的话规律太多,多一些物品也就少一些规律。

因此现在你面对的问题是这样的:有若干个物品(至少 3 个)排成一圈,你要念一句长度为 \(n (n ≥ 3)\) 的咒语,一开始手指指向任意一个物品,从第二个字开始,每念一个字,手指移动到顺时针的下一个物品。

如果最终指向的物品不是初始指向的物品,也不是与初始物品相邻的物品,那么就认为这个选择是比较随机的。

给定 \(n\),你想知道 \(3, \dots, n\) 这些数字有多少个作为物品数量的时候,选择是比较随机的。

思路

假如现在有 \(k\) 个物品,要指到自己本身,则必须满足 \(n\bmod k=1\),也就是 \((n-1)\bmod k=0\)

如果指到下一个,则满足 \(n\bmod k=2\),即 \((n-2)\bmod k=0\)

如果指到上一个,则 \(n\bmod k=0\)

那结果就是 \(n\) 减去不符合要求的数目,也就是 \(n, n-1, n-2\) 的因数,因为这三个数任意两数的gcd必然小于3,所以可以直接 \(O(\sqrt n)\) 求一遍就行。

小数据最好特判一下。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline 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<<1)+(x
<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, T;

void suan(int x)
{
	for(int i=1; i*i<=x; ++i)
	{
		if(x%i) continue; 
		if(i>3) ++m; 
		if(x/i>3 && i*i!=x) ++m; 
	}
//	printf("%lld\n", m); 
}

signed main()
{
	
	T=read(); 
	while(T--)
	{
		n=read(); m=3; 
		if(n<=5) 
		{
			printf("0\n"); 
			continue; 
		}
		suan(n); suan(n-1); suan(n-2); 
		printf("%lld\n", n-m); 
	}
	return 0;
}

总结

这道题难度不大,但注重细节。

对于小于等于3的情况,一定要特判好。

类似涉及这些质数因数的题目一定要注意小数据。

D2T2 网页浏览

题目

我们在上网时,从一个网页上的链接打开另一个网页有两种方式,一个是直接替换正在浏览的页面,另一个是在新标签页中打开。如果善用这两种打开方式,是可以节省一些操作的。

今天 Zayin 需要浏览 \(n\) 个网页,其中有且仅有一个网页是保存在本地可以点击打开的,其他 \(n − 1\) 个网页都是需要从某个页面的链接里打开的。因此,这 \(n\) 个网页的链接关系可以用一棵 \(n\) 个节点的有根树表示。

一开始,浏览器里什么标签页也没有,Zayin 可以点击根节点代表的网页,打开第一个标签页,接下来,Zayin 可以做如下的事情:

  • 从当前浏览的网页所含有的链接里选择一个打开,直接替换当前的网页;
  • 从当前浏览的网页所含有的链接里选择一个,在新标签页中打开;
  • 点击“返回”,当前网页返回被其替换的网页,该操作只能用于“替换打开”的网页;
  • 关闭当前网页。

Zayin 可以自行决定浏览顺序。求 Zayin 从空浏览器开始,到最后浏览完全部 \(n\) 个网页并全部关闭回到空浏览器的状态,最少需要多少步操作。

思路

对于任意一个节点,假设其有 \(m\) 个儿子,我们让其中 \(m-1\) 个儿子实行新建标签页操作,最后一个儿子实行直接替换操作。

如果 \(m=0\),也就是说这个点是叶子节点,就直接实行删除操作。

显然,每个节点的打开都需要一个操作。

而每个叶子节点都要无中生有,多实行一个操作。

所以答案为树的节点个数+叶子节点的个数。

代码

#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline 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<<1)+(x
<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 200010
//#define M
//#define mo
int n, m, i, j, k, T; 
int a[N]; 

signed main()
{
//	freopen("webpage.in", "r", stdin);
//	freopen("webpage.out", "w", stdout);
	n=read(); 
	for(i=1; i<=n; ++i) 
	{
		j=read(); 
		if(j!=-1) a[j]=1; 
	}
	for(i=1; i<=n; ++i)
	 	if(a[i]!=1) ++k; 
	printf("%d", n+k); 
	return 0;
}

总结

很巧妙的思维题,考试时想不到,打了个错的dp。

对于这种题目,我们最重要的是分析。

分析在什么时候是最优的,例如替换有时比新建更优。因为替换后只需关闭一次,而新建则要关闭很多次。

下次遇到类似题目要学会分析每个操作的代价。

D2T3 教室的电子钟

题目

为了迎接 GDOI,小蒟蒻学校所有教室的钟都换成了电子钟。电子钟会显示年月日时分秒的信息,每天的时间从 00:00:00 到 23:59:59。电子钟共有 14 个格子,共 14 × 7 = 98 个 LED 灯管组成。

image

具体来说,每个格子都是下面的十种状态中的一种,可以看到每个数字都是有 7 个 LED 灯管的亮暗状态呈现的:

image

这个钟的设计非常奇怪,每个灯管保持亮和保持暗的时候都是不消耗电的,每当由暗变成亮或者由亮变成暗则要消耗 1 个单位的电。小蒟蒻想知道,在需要考虑平闰年的真实情况下,从 \(X\)\(Y\)\(Z\)\(A\)\(B\)\(C\) 秒起,到 \(X′\)\(Y ′\)\(Z′\) 日到 \(A′\)\(B′\)\(C′\) 秒一共消耗了多少单位的电。

为了解决这道题,小蒟蒻想向你介绍一些他在科学课上学到的知识。

对于平年来说,每个月对应的天数如下:

image

对于闰年来说,每个月对应的天数如下:

image

其中闰年指的是年份能被 4 整除且不能被 100 整除,或者能被 400 整除的年份。平年则是不是闰年的年份。例如 2020 年是闰年,1900 年不是闰年。

image

思路

对于前70分,暴力模拟即可。

我们先弄出一个 \(10 \times 10\) 的表格,分别代表每个数字变成另一个数字所需要的代价,然后一秒一秒跳。

对于后30分,我们可以把年和后面的分开。先预处理平年和闰年情况变为常量,然后每一年分类讨论即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline 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<<1)+(x
<<3)+(ch^48);ch=getchar();}return x*f;}
//#define N
//#define M
//#define mo
int n, m, i, j, k, T;
int mp[10]={6, 2, 5, 5, 4, 5, 6, 3, 7, 6}; 
int b[10][10]=
{
  0, 4, 3, 3, 4, 3, 2, 3, 1, 2, 
  4, 0, 5, 3, 2, 5, 6, 1, 5, 4, 
  3, 5, 0, 2, 5, 4, 3, 4, 2, 3, 
  3, 3, 2, 0, 3, 2, 3, 2, 2, 1, 
  4, 2, 5, 3, 0, 3, 4, 3, 3, 2, 
  3, 5, 4, 2, 3, 0, 1, 4, 2, 1, 
  2, 6, 3, 3, 4, 1, 0, 5, 1, 2, 
  3, 1, 4, 2, 3, 4, 5, 0, 4, 3, 
  1, 5, 2, 2, 3, 2, 1, 4, 0, 1, 
  2, 4, 3, 1, 2, 1, 2, 3, 1, 0
}; 
int mth[15]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0, 0}; 
int n1, yu1, r1, s1, f1, m1; 
int n2, yu2, r2, s2, f2, m2; 
int ans=0; 
const int ping_nian=106904682; 
const int run_nian=107197568;  
 
void bian(int x, int y, int z)
{
    int sum=0; 
//  printf("%lld->%lld: ", x, y); 
    while(z--) sum+=b[x%10][y%10], x/=10, y/=10; 
//  printf("%lld\n", sum); 
    ans+=sum; 
}
 
int pan(int x)
{
    if(x%400==0) return 1; 
    if(x%100==0) return 0; 
    if(x%4==0) return 1; 
    return 0; 
}
 
int cong()
{
    while(n1!=n2 || yu1!=yu2 || r1!=r2 || s1!=s2 || f1!=f2 || m1!=m2)  
    {
        if(m1<59) bian(m1, m1+1, 2), ++m1; 
        else
        {
            bian(m1, 0, 2); m1=0; 
            if(f1<59) bian(f1, f1+1, 2), ++f1; 
            else
            {
                bian(f1, 0, 2); f1=0; 
                if(s1<23) bian(s1, s1+1, 2), ++s1; 
                else
                {
                    bian(s1, 0, 2); s1=0; 
                    if(r1<(yu1==2 ? (pan(n1) ? 29 : 28) : mth[yu1])) bian(r1, r1+1, 2), ++r1; 
                    else
                    {
                        bian(r1, 1, 2); r1=1; 
                        if(yu1<12) bian(yu1, yu1+1, 2), ++yu1; 
                        else
                        {
                            bian(yu1, 1, 2); yu1=1; 
                            bian(n1, n1+1, 4); ++n1;  
                        }
                    }
                }
            }
        }
    }
    return ans; 
}
 
void zhuan() 
{
	while(n1+1<n2)
	{
		if(pan(n1)) 
		{
			if(yu1<2) ans+=run_nian; 
			else if(yu1==2 && r1<29) ans+=run_nian; 
			else ans+=ping_nian; 
		}
		else if(pan(n1+1))
		{
			if(yu1<2) ans+=ping_nian; 
			else if(yu1==2 && r1<29) ans+=ping_nian; 
			else ans+=run_nian; 
		}
		else ans+=ping_nian; 
		bian(n1, n1+1, 4), ++n1; 
	}
}
 
signed main()
{
	// 107197568
//	printf("%lld\n", qiu());
    scanf("%lld%lld%lld%lld%lld%lld", &n1, &yu1, &r1, &s1, &f1, &m1); 
    scanf("%lld%lld%lld%lld%lld%lld", &n2, &yu2, &r2, &s2, &f2, &m2); 
	zhuan(); 
    printf("%lld", cong()); 
//	printf("%lld", qiu()); 
    return 0;
}

总结

细节处理题。

对于做日历类的数据处理题的时候,有几点需要注意:

  • 平年闰年在什么时候
  • 2月前和2月后该算成哪一年

对于电子钟类题目,要注意是求的是变化还是亮的个数。

D2T4 机器人

题目

刚上初一的小纯特别喜欢机器人,这周末,她报名了学校的“小机器人俱乐部”,而进入俱乐部需要通过一场考试。

考试场地可以看作一个 \(n \times m\) 的网格图,行从上往下标号为 \(1, \dots, n\),列从左往右标号为 \(1, \dots , m\)。每个格子有三种可能:空地,障碍物,机器人(有且只有一个),分别用“.”、“*”、“R”表示。现在小纯需要控制机器人在考试场地上行走,她每秒可以发送一条指令,为“W”——往上走、“S”——往下走、“A”——往左走、“D”——往右走的其中之一。由于俱乐部的机器人是避障机器人,当机器人接收到一条指令的时候,如果即将到达的位置为障碍物,那么机器人将留在原地,否则机器人向对应方向走一步。如果其走出边界则考试失败(例如在第一行发送指令向上走)。

为了增加考试的难度,俱乐部提供的机器人的信号接收器都存在问题。换言之,对于小纯给出的每一条指令,机器人有可能接收到指令并执行,也有可能接收不到指令并保持不动。现在小纯提供了一个指令序列,她想知道,这个指令序列是否存在某些情况使得这个指令序列走出边界。

image

思路

对于每个格子,显然,我们越早到这个格子是越优的。

基于此,我们可以对第一个点入队,查找它通往上下左右点最早在哪里,跑个最短路即可。

最后用Dij+优先队列优化,如果跑出格子了就输出 YES

代码

#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline 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<<1)+(x
<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 610
#define M 200010
//#define mo
struct node
{
	int x, y, z; 
	bool operator <(const node &A) const
	{
		return z>A.z; 
	}
}p; 
int n, m, i, j, k, T;
int dx[4]={-1, 1, 0, 0}; 
int dy[4]={0, 0, -1, 1}; 
char s[N][N], c[M]; 
int a[N][N], nxt[M][5], len; 
int ax, bx, cx, ex, sx, sy; 
int x, y, z, newx, newy, flg;  
priority_queue<node>q; 

void init()
{
	ax=bx=cx=ex=flg=i=j=k=len=m=n=newx=newy=sx=sy=x=y=z=0; 
	memset(a, -1, sizeof(a)); 
	memset(c, 0, sizeof(c)); 
	memset(s, 0, sizeof(s)); 
	memset(nxt, 0, sizeof(nxt)); 
	while(!q.empty())  q.pop(); 
}

signed main()
{
//	freopen("robot.in", "r", stdin);
//	freopen("robot.out", "w", stdout);
	T=read(); 
	while(T--)
	{
		init(); 
		scanf("%s", c+1); len=strlen(c+1); 
		n=read(); m=read(); 
		for(i=1; i<=n; ++i) 
		{
			scanf("%s", s[i]+1); 
			for(j=1; j<=m; ++j)
				if(s[i][j]=='R') sx=i, sy=j; 
		}
		ax=bx=cx=ex=0; 
		for(i=len; i>=0; --i)
		{
			nxt[i][0]=ax; nxt[i][1]=bx; 
			nxt[i][2]=cx; nxt[i][3]=ex; 
			if(c[i]=='W') ax=i; 
			if(c[i]=='S') bx=i; 
			if(c[i]=='A') cx=i; 
			if(c[i]=='D') ex=i; 
		}
//		for(i=0; i<=len; ++i)
//			printf("%d %d %d %d\n", nxt[i][0], nxt[i][1], nxt[i][2], nxt[i][3]); 
		p.x=sx; p.y=sy; p.z=0; 
		q.push(p); 
		while(!q.empty())
		{
			p=q.top(); q.pop(); 
			if(a[x=p.x][y=p.y]!=-1) continue; 
			a[x][y]=z=p.z; 
			for(i=0; i<4; ++i)
			{
				if(!nxt[z][i]) continue; 
				newx=x+dx[i]; 
				newy=y+dy[i]; 
				if(a[newx][newy]!=-1) continue; 
				if(s[newx][newy]=='*') continue; 
				if(newx<1 || newy<1 || newx>n || newy>m)
				{
					printf("YES\n"); flg=1; 
					break; 
				}
				p.x=newx; p.y=newy; p.z=nxt[z][i]; 
				q.push(p); 
			}
			if(flg) break; 
		}
//		for(i=1; i<=n; ++i, printf("\n")) 
//			for(j=1; j<=m; ++j) printf("%2d ", a[i][j]); 
		if(!flg) printf("NO\n"); 
	}
	return 0;
}

总结

这题思维难度中等,想起来有一定难度。

对于每个格子,我们要考虑其最优情况是怎样,想清这一点,就迎刃而解了。

多测队列要清空!我因此没了90分。

posted @ 2022-04-25 18:02  zhangtingxi  阅读(112)  评论(0编辑  收藏  举报