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
解题思路:
首先需要有两个前置知识:
如果我们能把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);
}