7月20日基础练习

T1,T2
都是蓝书上贪心专题例题,看书就行了
T3
【题目描述】
在一个字符串S上应用以下操作K次,可以得到多少个不同的字符串? 选择一个小写的英文字母并将它插入到S的某个位置
答案可能是巨大的,所以输出答案模(1e9+7)的值。

【输入格式】string.in
第一行输入一个整数K(1≤K≤1e6)。
第二行输入1个字符串S。S由小写英文字母组成,并且满足:1≤|S|≤1e6。

【输出格式】string.out
输出一个整数,即不同的字符串数量模(1e9+7)的值。

【输入样例1】
5
oof

【输出样例1】
575111451

【样例1说明】
例如,我们可以获得proofend、moonwolf和onionpuf,
而不能获得oofsix、oofelevennn、voxafolt或foooooo。
——————————————————————————
拿到这题,我第一反应是直接加字符,26*26这样乘下去,但这样会有重复的字符串产生,并且我不知道如何去算重复的个数。
于是有一个很巧妙的做法,首先像样例1如果产生了aoofovof这样的字符串,我们默认最后的“ovof”中的oof是原本的oof,就将第一个o为分界线,原先字符串被分为“aoof”和"ovof"两部分,并且我们发现前面一段中字母可以随便取,于是当在前面的字母数为i时,临时的ans便乘上quick_pow(26,i),后面的一段因为只有1个"oof",所以在往里面加字母时不能加原串中的上一个字母,所以每个位置字母只能选25种,于是ans再乘以quick_pow(25,n-i),然后考虑后面字母的位置,便是需要排列组合公式:
在这里插入图片描述
又因为右边字符串第一个字母是固定了的,所以ans再乘以C(n+m-i-1,m-1)。
附上std

#include<bits/stdc++.h>
using namespace std;

#define mod 1000000007
#define LL long long
#define maxn 1000010

char s[maxn];
LL fact[maxn<<1],inv[maxn<<1];
int m,n;

LL ksm(LL a, LL b) { return (b % 2 == 1? a : 1LL) * (b == 0? 1LL : ksm(a*a%mod, b/2)) % mod; }
LL quick_pow(LL a,LL b)  //快速幂
{
	LL ret=1;
	while(b)
	{
		if(b&1)ret=ret*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ret%mod;
}

void init()
{
	fact[0]=1;
	for(int i=1;i<=n+m;i++)
		fact[i]=fact[i-1]*i%mod;         //阶乘
	inv[n+m]=quick_pow(fact[n+m],mod-2);
	for(int i=n+m-1;i>=0;i--)
		inv[i]=inv[i+1]*(i+1)%mod;       //阶乘的乘法逆元
}

LL C(int n,int m)  //组合数计算
{
	return fact[n]*inv[n-m]%mod*inv[m]%mod;
}

int main()
{
	//freopen("string.in","r",stdin);
	//freopen("string.out","w",stdout);
	LL ans=0;
	scanf("%d%s",&n,s);
	m=strlen(s);
	init();
	for(int i=0;i<=n;i++)
		ans=(ans+quick_pow(26,i)*C(n+m-i-1,m-1)%mod*quick_pow(25,n-i)%mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

T4
有一棵有N个结点的树,第i条边双向连接结点Ai和Bi。
Tom站在结点u,Jerry站在结点v。
现在,他们将玩一个标记游戏,玩法如下所示:
(1)如果Tom和Jerry站在同一个结点,游戏结束;否则,Tom移动到他选择的结点,
该结点与他当前的结点相邻。
(2)如果Tom和Jerry站在同一个结点,游戏结束;否则,Jerry移动到他选择的结
点,该结点与他当前的结点相邻。
(3)回到步骤(1)。
轮到某人走时,不能停留在同一个结点。
Tom希望回合数尽可能多,而Jerry则希望回合数尽可能少,两人都知道对方的位置和
策略,并且自己都采用最优策略。
可以证明这场比赛一定会结束。
输出游戏结束时Jerry走的步数。

【输入样例】
第一行输入三个正整数N,u,v,相邻数之间由一个空格隔开。
接下来有N-1行,每行两个正整数Ai和Bi,表示结点A和结点B有一条双向边相连。

【输出格式】
输出一个整数,即游戏结束时Jerry走的步数。

【输入样例1】
5 4 1
1 2
2 3
3 4
3 5
在这里插入图片描述

【输出样例1】
2

【样例1说明】
如果两个玩家都玩得最好,游戏将按如下方式进行:
•Tom移动到结点3。
•Jerry移动到结点2。
•Tom移动到结点5。
•Jerry移动到结点3。
•Tom移动到结点3。
在这里,Jerry执行了两个操作步骤。
题解
首先,Jerry的最优策略很好确定,就是走通向的当前Tom的位置的简单路径就可以了。同时,不论Tom的位置怎样变化,Jerry走的路径都会沿着通向最终会抓到Tom的结点的这条道路,证明很难,我就不证明了。感性地理解,就是由于Tom和Jerry不会在途中相遇,所以Jerry走向的那个子树(以当前Jerry所在结点为根)肯定包含Tom和游戏结束时的Tom的终点。
那么Tom的策略呢就有点麻烦,所以我们就用搜索判断:
首先,我们预处理出Jerry到每个结点的距离,之后搜索Tom的路径(深搜、广搜皆可,反正别学我个憨憨用lca )。对于当前结点是否可以走,只需要满足Tom到这里的距离严格小于Jerry到这里的距离即可。特殊地,由于只能移动,不能停留在同一个结点,所以到了叶子结点时,Tom会在相邻结点和这个叶子结点之间重复移动,最终抓到的位置一定是在相邻结点上(一共两种情况,自己手推一下就可以发现)。
代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int ans,du[N],tot,n,u,v,first[N],Next[N*2],to[2*N],x,y,dis[N],sum,l,r,q[N];
void add(int a,int b)
{
	tot++;
	Next[tot]=first[a];
	first[a]=tot;
	to[tot]=b;
}
void dfs(int st,int fa)
{
	if(sum>=dis[st])
	{
		ans=max(ans,dis[st]);
		return;
	}
	for(int i=first[st];i;i=Next[i])
	{
		if(to[i]==fa)
		continue;
		sum++;
		dfs(to[i],st);
		sum--;
	}
	if(du[st]==1)
    ans=max(ans,dis[st]-1);
}
int main()
{
	cin>>n>>u>>v;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x),du[x]++,du[y]++;
	}
	memset(dis,-1,sizeof(dis));
	dis[v]=0;
	r++;
	q[r]=v;
	while(l<r)
	{
		l++;
		int U=q[l];
		for(int i=first[U];i;i=Next[i])
		{
			int V=to[i];
			if(dis[V]==-1)
			{
				dis[V]=dis[U]+1;
				r++;
				q[r]=V;
			}
		}
	}
	dfs(u,0);
	cout<<ans;
	return 0;
}

T5

【题目描述】
对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:例如一数列4 2 4 5 1要分成3段将其如下分段:[4 2][4 5][1]第一段和为6,第2段和为9,第3段和为1,和最大值为9。 将其如下分段:[4][2 4][5 1]第一段和为4,第2段和为6,第3段和为6,和最大值为6。 并且无论如何分段,最大值不会小于6。所以可得到要将数列4 2 4 5 1要分成3段,每段和的最大值最小为6。

【输入格式】
输入文件divide.in的第1行包含两个正整数N,M;
第2行包含N个空格隔开的非负整数A[i],含义如题目所述。

【输出格式】
输出文件divide.out仅包含一个正整数,即为每段和最大值的最小值。

【样例输入】
5 3
4 2 4 5 1

【样例输出】
6

【数据范围】
对于20%的数据,有N≤10;
对于40%的数据,有N≤1000;
对于100%的数据,有N≤100000;M≤N;A[i]之和不超过1e9.
少见的最后一题是水题…问最大值的最小值,很显然是二分,而且都是正整数,就直接做了
附考场代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,l,r,mid,a[N],tot,sum,Max;
inline int read()
{
	int x=0,f=1;
	char ch;
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}
int Get(int x)
{
	tot=0;
	sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=a[i];
		if(i==n)
		tot++;
		if(sum>x)
		{
			sum=a[i];
			tot++;
		}
	}
	return tot;
}
int main()
{
	//freopen("divide.in","r",stdin);
	//freopen("divide.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	a[i]=read(),sum+=a[i],Max=max(Max,a[i]);
	l=0;
	r=sum;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(mid<Max)
		{
			l=mid+1;
			continue;
		}
		if(Get(mid)>m)
		l=mid+1;
		else
		r=mid;
	}
	cout<<l;
}

难得写了次博客啊…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值