杭电ACM 暑假个人pk赛(1)

7-1 争执

n 个人从左往右排成一队,每个人要么朝向左边,要么朝向右边。

这 n 个人彼此之间的关系都不太融洽,所以如果两个相邻的人面对面站着(左边的人朝向右边,右边的人朝向左边),那么他们就会发生争执,其中一个人会将另外一个人赶出队伍。

每次争执,任意两种情况(左边的人或者右边的人被赶出队伍)都有可能发生;而对于同时存在的多个争执,它们将会按照任何可能的顺序依次发生,但是不会有两个争执同时发生。

请求出,当队伍稳定后,即任意两个人都不会发生争执时,队伍中还剩下人数的最小可能值。

输入格式:

第一行包含一个正整数 n(n<=10^6),表示人数。

第二行包含一个长度为 n 的字符串,从左往右表示每个人的朝向,其中“L”表示朝左,“R”表示朝右。

输出格式:

输出一行一个整数,即剩下人数的最小可能值。

输入样例:

6
LRRLRL

输出样例:

2

[数据解释]一种可能达到 2 人的方式是 LRRLRL→LRLRL→LRLRL→LRRL→LRL→LR。

时间限制 1000 ms 内存限制 64 MB

解题思路:

贪心。从左往右:当遇到R时入栈;当遇到L时,把栈中的R消至仅剩一个,然后自己消除。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int n,ans;
char s[maxn];
inline char get_ch(){
    char ch=getchar();
    while (ch!='L'&&ch!='R')
        ch=getchar();
    return ch;
}
int main(){
   // freopen("std.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        s[i]=get_ch();
    int sta=0;
    for (int i=1;i<=n;i++)
        if (s[i]=='R')
            sta++;
        else{
            if (sta==0)
                ans++;
            else
                sta=1;
        }
    ans+=sta;
    printf("%d\n",ans);
    return 0;
}

7-2 寻找字符串

对 于 一 个 长 度 为 n 的 小 写 字 母 组 成 的 字 符 串 S[1..n], 定 义 它 的 差 异 度 f(S) 为 f(S) =∑|S[i] - S[i + 1]| (i=1..n-1)。其中两个字符的差值定义为 ASCII 码的差值。

给定一个正整数 k,请找到一个长度最短的小写字符串 S,满足 f(S) = k,若有多个长度最短的,输出字典序最小的。

对于两个长度都为 m 的字符串 A, B,我们认为 A 的字典序小于 B 当且仅当存在一个 i(1 ≤ i ≤ m),满足 A[1..i-1] = B[1..i-1] 且 A[i] < B[i]。

输入格式:

输入数据每行包含一个正整数 k(k<=10^7)。

输出格式:

输出一行一个小写字符串 S,即你找到的字符串。

输入样例:

3

输出样例:

ad

时间限制 1000 ms 内存限制 64 MB

解题思路:

贪心。先考虑如何使字符串最短:排列为类似“azaz...”组合,直接len=n/25得到这个最小长度。

再考虑如何使字典序最小:首先一定是a开头的;对于当前这一位,考虑从‘a’枚举至‘z’,若我们选择了字母ch,那么此时要使字符串最短一定是ch后接‘a’或‘z’(取决于离哪个远),然后重复‘azaz...’。如果此时最短字符串长度仍为len,那么说明当前的选择是可行的,继续往后枚举即可。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxL=1e7;
char s[maxL];
int n,len;
int main(){
	scanf("%d",&n);
	len=(n+24)/25+1;
	s[1]='a';
	for (int i=2;i<=len;i++){
		for (char j='a';j<='z';j++){
			int Max=abs(j-s[i-1]);
			if (i<len){
				Max+=max(abs(j-'a'),abs(j-'z'));
				Max+=(len-i-1)*25;
			}
			if ((Max>=n&&i!=len)||(Max==n&&i==len)){
				n-=abs(j-s[i-1]);
				s[i]=j;
				break;
			}
		}
	}
	printf("%s\n",s+1);
	return 0;
}

7-3 硬币游戏

在游戏开始之前,n 枚硬币顺时针放在一个环上,第 i 枚硬币的面值为 wi。

在游戏的每一步,你需要选择其中某枚硬币,将其放置在它顺时针方向下一枚硬币的位置,并将被覆盖掉的硬币拿走。这一步你的得分为这两枚硬币面值差值的绝对值。在这之后你需要继续操作,直到只剩下一枚硬币为止,你的最终得分即为每一步操作得分之和。

请写一个程序,找到最佳的策略,使得最终得分最大。

输入格式:

第一行包含一个正整数 n(n<=300),表示硬币的数量。

第二行包含 n 个正整数 w1, w2, ..., wn(1 ≤ wi ≤ 10^6),按顺时针依次表示每枚硬币的面值。

输出格式:

输出一行一个整数,即最终得分的最大值。

输入样例:

4
3 4 5 6

输出样例:

6

时间限制 1000 ms 内存限制 64 MB

解题思路:

DP,f[i][j]表示区间[i,j]全部取完的最大贡献(此时剩下的一定是第i枚硬币),转移方程:

f[i][j]=max(f[i][k]+f[k+1][j]+|a[i]-a[k+1]|)

题目要求是个环,我们把a[n]扩展成a[2n],最终答案就是f[i][i+n-1]的最大值。

转移可通过记忆化DFS实现。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=305;
int f[maxn*2][maxn*2],n,a[maxn*2],ans;
int xx,yy;
int DFS(int i,int j){
	if (f[i][j]!=-1)
		return f[i][j];
	for (int k=i;k<j;k++)
		f[i][j]=max(DFS(i,k)+DFS(k+1,j)+abs(a[i]-a[k+1]),f[i][j]);
	return f[i][j];
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),a[n+i]=a[i];
	memset(f,255,sizeof(f));
	for (int i=1;i<=n;i++)
		f[i][i]=f[i+n][i+n]=0;
	ans=DFS(1,n),xx=1,yy=n;
	for (int i=2;i<=n;i++){
		 ans=max(ans,DFS(i,i+n-1));
	}
	printf("%d\n",ans);
	return 0;
}

7-4 排列

在数学中,定义 n 的排列是一个长度为 n 的正整数序列 p1, p2, . . . , pn,其中 1 ≤ pi ≤ n,且所有 pi互不相同。
显然,n 的排列一共有 n! 种情况。在这道题中,给定另外一个序列 a1, a2, . . . , an,请统计有多少个长度为 n 的排列是好排列。
一个排列是好排列,当且仅当对于所有 i ∈ [1, n] 都有 pi ≤ ai。

输入格式:

第一行包含一个正整数 n(n<=10^5)。
第二行包含 n 个正整数 a1, a2, . . . , an (1 ≤ ai ≤ n)。

输出格式:

输出一行一个整数,即好排列的数量,因为答案可能很大,请对 10^9+7 取模输出。

输入样例:

输入样例1:

3
3 3 3

输入样例2:

3
1 3 3

输出样例:

输出样例1:

6

输出样例2:

2

时间限制 400 ms 内存限制 64 MB

解题思路:

简单计数问题。排升序,a[i]的贡献为a[i]-i+1(已经选了i-1个数),如果出现负数或者0则说明无解。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005,tt=1e9+7;
typedef long long LL;
int n,cnt,a[maxn];
LL ans;
inline int read(){
	int ret=0; char ch=getchar(); bool fl=0;
	while (!isdigit(ch))
		fl^=!(ch^'-'),ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return fl?-ret:ret;
}
int main(){
	n=read();
	for (int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+n+1);
	ans=a[1],cnt=1;
	for (int i=2;i<=n;i++){
		if (a[i]==cnt){
			ans=0;
			break;
		}
		ans=((LL)a[i]-cnt)*ans%tt,cnt++;
	}
	printf("%lld\n",ans);
	return 0;
}

7-5 三维箱子

小 Q 会在三维空间中依次放入 n 个长方体箱子,编号为 1 到 n。这些箱子长度都为 dx,宽度都为 dy,高度都为 dz。所有箱子的每条棱都与坐标轴平行。对于第 i 个箱子,它的一个顶点坐标为(xi, yi, zi),与该顶点相对的另一个顶点的坐标为 (xi + dx, yi + dy, zi + dz)。

对于两个不同的箱子 i 和 j,它们存在公共点,当且仅当 |xi - xj | ≤ dx, |yi - yj | ≤ dy, |zi - zj | ≤ dz都成立。

一开始三维空间中没有任何箱子。小 Q 现在按照 1 到 n 的顺序依次往三维空间中添加每个箱子,如果加入箱子 i 后,它与之间加入的某个箱子有公共点,请输出这个 i。

若有多个这样的 i,请输出最小的;若没有这样的 i,请输出 0。

输入格式:

第一行包含四个正整数 n, dx, dy, dz,表示箱子的数量以及尺寸(2 ≤ n ≤ 10^5, 1 ≤ dx, dy, dz, xi, yi, zi ≤ 10^9)。
接下来 n 行,每行三个正整数 xi, yi, zi,依次表示每个箱子的顶点坐标。

输出格式:

输出一行一个整数,即答案。

输入样例:

输入样例1:

2 2 3 4
1 1 1
3 4 5

输入样例2:

2 2 3 4
1 1 1
3 4 6

输出样例:

输出样例1:

2

输出样例2:

0

时间限制 400 ms 内存限制 64 MB

解题思路:

很巧妙的分块思想。

考虑把(x,y,z)这个点丢入(x/dx,y/dy,z/dz)这个块中,那么:

1.每个块中最多只能有一个元素,如果有两个元素这两个元素就相交了。

2.每个块中的元素,若要发生相交,则一定是与周围26个(3*3*3-1)块中的元素相交。此时因为(1),所以最多只需要与26个元素比较。

综上,每次给新加的点分块,与周围判断相交即可。

分块过程可以用map解决。

AC代码:

#include <bits/stdc++.h>
using namespace std;
struct Node{
	int x,y,z;
	bool operator <(const Node &B) const{
		if (x!=B.x)
			return x<B.x;
		if (y!=B.y)
			return y<B.y;
		return z<B.z;
	}
};
map<Node,Node> mp;
int n,dx,dy,dz;
inline int read(){
	int ret=0; char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return ret;
}
int main(){
	n=read(),dx=read(),dy=read(),dz=read();
	for (int i=1;i<=n;i++){
		int x=read(),y=read(),z=read();
		int xx=x/dx,yy=y/dy,zz=z/dz;
		for (int d1=-1;d1<=1;d1++)
			for (int d2=-1;d2<=1;d2++)
				for (int d3=-1;d3<=1;d3++){
					if (mp.find((Node){xx+d1,yy+d2,zz+d3})==mp.end())
						continue;
					Node a=mp[(Node){xx+d1,yy+d2,zz+d3}];
					if (abs(a.x-x)<=dx&&abs(a.y-y)<=dy&&abs(a.z-z)<=dz){
						printf("%d\n",i);
						return 0;
					}
				}
		mp[(Node){xx,yy,zz}]=(Node){x,y,z};
	}
	printf("0\n");
	return 0;
}	

7-6 距离

题目描述:

比特镇有 n 个景点,依次编号为 1, 2, . . . , n。这些景点之间通过 n - 1 条双向道路连接,所有景点都是连通的。对于每个 i(i > 1),都有一条从景点 i 到景点 ⌊ i/2 ⌋ 的双向道路。

接下来有 m 个操作,操作有以下两种形式:

• - u,删除连接景点 u 和景点 ⌊u/2 ⌋ 之间的双向道路。输入保证每条道路最多被删除一次。

• ? k,询问有多少对 i, j(1 ≤ i < j ≤ n) 满足景点 i 和景点 j 仍然连通,且存在一条从 i 到 j 的道路数量不超过 k 的路径。

输入格式:

第一行包含两个正整数 n , m (n ≤ 100000, m ≤ 100000)表示景点和操作的数量。

接下来 m 行,每行描述一个操作。

对于 100% 的数据,2 ≤ u ≤ n,1 ≤ k ≤ 40。

输出格式:

对于每个询问输出一行一个整数,即满足条件的景点对数。

输入样例:

5 5
? 2
- 2
? 2
- 5
? 2

输出样例:

8
4
2

时间限制 400 ms 内存限制 64 MB

解题报告:

首先要注意到这是满二叉树。大致做法就是维护一下距离为i的点对数量(数组ans),每次删边(u,v)(u是儿子)的时候考虑会对ans的影响。大致可以分成两个部分:从u的子树到v的子树,从u的子树走到v的祖先及其子树。

第一部分可以通过维护一下u,v为根的子树中,各种深度的节点有多少个来解决。

第二部分我写的有点丑陋,大致就是向上遍历,然后减去v为根的子树各种深度的节点数量(排除v的子树)

复杂度两个log,能过。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
typedef long long LL;
int cnt[maxn][20],n,m,tmp[41],dep[20];
bool fa[maxn];
LL ans[41];
inline int read(){
	int ret=0; char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return ret;
}
inline char read_ch(){
	char ch=getchar();
	while (ch!='?'&&ch!='-')
		 ch=getchar();
	return ch;
}
void Build(){
	memset(cnt,0,sizeof(cnt));
	for (int i=n;i;i--){
		cnt[i][0]++;
		if (i==1)
			break;
		int Fa=i>>1;
		for (int j=0;j<=17;j++)
			cnt[Fa][j+1]+=cnt[i][j];
	}
	for (int i=1;i<=n;i++){
		for (int j=1;j<=17;j++)
			ans[j]+=cnt[i][j];
		int lc=(i<<1),rc=(i<<1)+1;
		if (lc>n||rc>n)
			continue;
		for (int j=0;j<=17;j++)
			for (int k=0;k<=17;k++)
				ans[2+j+k]+=(LL)cnt[lc][j]*cnt[rc][k];
	}
}
void Solve(int x){
	int Fa=x>>1;
	for (int i=1;i<=17;i++)
		cnt[Fa][i]-=cnt[x][i-1];
	//从上方来
	int p=Fa,dis=1;
	memset(tmp,0,sizeof(tmp));
	while (fa[p]){
		for (int i=dis+1;i<=17;i++)
			cnt[p>>1][i]-=cnt[x][i-dis-1];
		dep[0]=1;
		for (int i=1;i<=17;i++)
			dep[i]=cnt[p>>1][i]-cnt[p][i-1];
		for (int i=0;i<=17;i++)
			tmp[i+dis]+=dep[i];
		p>>=1,dis++;
	}
	for (int j=1;j<=40;j++)
		for (int i=0;i<min(18,j);i++)
			ans[j]-=(LL)cnt[x][i]*tmp[j-i-1];
	//两边
	for (int i=0;i<=17;i++)
		for (int j=0;j<=17;j++)
			ans[i+j+1]-=(LL)cnt[Fa][i]*cnt[x][j];
	fa[x]=0;
}
int main(){
	n=read(),m=read();
	Build();
	memset(fa,1,sizeof(fa)),fa[1]=0;
	while (m--){
		char op=read_ch();
		int x=read();
		if (op=='?'){
			LL sum=0;
			for (int i=1;i<=x;i++)
				sum+=ans[i];
			printf("%lld\n",sum);
		} else{
			Solve(x);
		}
	}
	return 0;
}

7-7 最大公约数

给定一个序列a[1..n],现在要从中选择一个非空子集,显然有2n−1种选法。
求有多少个非空子集的数字的最大公约数为1。

输入格式:

第一行包含一个正整数n(1≤n≤105)。
第二行包含n个正整数a[1],a[2],...,a[n] (1≤a[i]≤106)。

输出格式:

输出一行一个整数,即答案对1000000007取模的结果。

输入样例:

在这里给出一组输入。例如:

3
2 4 3

输出样例:

3

时间限制 400 ms 内存限制 64 MB

解题思路:

首先需要有两个前置知识:

容斥原理详解-CSDN博客

默比乌斯函数_百度百科

如果我们能把GCD为1以外的所有可能方案数都算出来,那么一减答案就出来了。

所以我们考虑GCD为x及其倍数的集合的方案数。我们只计算x为质数时候的情况。比如{12,60},GCD=12,我们只在2的情况下计算一次。

还有一个问题是比如{12,60},x=3的时候我们也会计算一次他们的贡献,这样子就重了,所以我们考虑容斥。计算集合A1...An的并,这里的Ai分别对应了当x为某一质数时,GCD为x的倍数的方案数。这就是我们要求的东西。

再考虑1e6范围内有近2w个质数,如果去二进制组合那是铁T的,为此我们引入莫比乌斯函数,不难发现莫比乌斯函数恰好就是我们容斥系数的相反数!

(我们Ai,Aj的并我们只考虑质因数出现一次的情况,这就是所谓“无平方数因数”)

接下来就是通过欧拉筛线性的求出这个欧拉函数即可。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxv=1000005,N=1000000,tt=1e9+7;
typedef long long LL;
int mu[maxv],prime[100000],n,cnt[maxv],ans;
bool isprime[maxv];
inline int read(){
	int ret=0; char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return ret;
}
int ksm(int a,int b){
	if (b==0)
		return 1;
	if (b==1)
		return a;
	int w=ksm(a,b>>1);
	if (b&1)
		return ((LL)w*w%tt)*a%tt;
	else
		return (LL)w*w%tt;
}
int add(int x,int y){
	return (((x+tt)%tt)+((y+tt)%tt))%tt;
}
void Build(){
    //mu为莫比乌斯函数
	memset(isprime,1,sizeof(isprime));
	isprime[1]=0,mu[1]=1;
	for (int i=1;i<=N;i++){
		if (isprime[i])
			prime[++prime[0]]=i,mu[i]=-1;
		for (int j=1;j<=prime[0]&&i*prime[j]<=N;j++){
			isprime[i*prime[j]]=0;
			if (i%prime[j]==0)
				break;
			mu[i*prime[j]]=-mu[i];//欧拉筛的性质保证了只会扫到一次。
		}
	}
}
int main(){
	Build();
	n=read();
	for (int i=1;i<=n;i++)
		cnt[read()]++;
	ans=add(ksm(2,n),-1);
	for (int i=2;i<=N;i++)
		if (mu[i]!=0){
			int sum=0;
			for (int j=i;j<=N;j+=i)
				sum+=cnt[j];
			int temp=((LL)mu[i]+tt)%tt*add(ksm(2,sum),-1)%tt;
			ans=add(ans,temp);
		}
	printf("%d\n",ans);
}

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值