S2考前综合刷题营Day3

欢乐

【问题描述】

你是能看到第一题的 \(friends\) 呢。

——hja

众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。

给定 \(N\),求一个 \(K\),使得 \(K!≥\frac{N!}{K!}\) 并且 \(K\) 尽量小。

【输入格式】

一行一个整数 \(N\)

【输出格式】

一行一个整数代表答案。

【样例输入】

10

【样例输出】

7

【数据规模与约定】

对于 \(30\%\) 的数据,\(N≤10\)

对于 \(60\%\) 的数据,\(N≤100\)

对于 \(80\%\) 的数据,\(N≤1000\)

对于 \(100\%\) 的数据,\(3≤N≤10^6\)

Solution

80pts

枚举 \(k\),利用高精算出 \(k!\)\(\frac{N!}{K!}\),然后求答案即可。
时间复杂度 \(O(n^2)\)

100pts

比较两个数的大小不一定要用原数的大小来比较,我们可以将它们同时取 \(log\)
所求式子就变成了:\(\log{K!}>=\log{\frac{N!}{K!}}\),利用 \(log\) 函数的性质,式子可以进一步化简:
\(\log{K!}>=\log{N!}-\log{K!}\),且有 \(\log{N!}=\log{1}+\log{2}+...+\log{N}\)
时间复杂度 \(O(n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1e6+5;
int n,ans;
double Log[N],S[N];
bool check(int x)
{
	return 2.0*S[x]>S[n];
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		Log[i]=(double)log(i)/log(2);
		S[i]=S[i-1]+Log[i];
	}
	int l=0,r=n+1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans);
	return 0;
}

水题

【问题描述】

你是能看到第二题的 \(friends\) 呢。

——aoao

众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。

现在有若干个任务,每个任务需要一定的执行时间,以及要求其依赖的所有任务执行完成后才能够执行。你可以同时做多个任务,但是一个任务一旦开始就不能停下必须做完。如果当前有多个任务可以做,按照这些任务到来的时间作为第一关键字,任务的名字作为第二关键字选择最小的任务执行。问执行完所有任务所需要的时间是多少。

【输入格式】

一行一个字符串。

对于每个任务,其格式为“任务名字:[依赖任务\(1\),依赖任务\(2\),……,依赖任务\(k\)]:执行时间”。不同任务与不同任务之间用分号分割。在输入完所有任务之后,会用“/”隔开一个整数,该整数代表同一时间最多执行多少个任务。

【输出格式】

一行一个整数代表答案。

【样例输入】

b:[a]:2;c:[a]:3;a:[]:1/2

【样例输出】

4

【数据规模与约定】

对于 \(30\%\) 的数据,任务数量 \(≤10\)

对于 \(60\%\) 的数据,任务数量 \(≤100\)

对于 \(100\%\) 的数据,任务数量 \(≤1000\),任务的名字长度小于等于 \(50\),所需要的执行时间不超过 \(100\),总依赖数量不超过 \(200000\)

Solution

其实这道题就是读入恶心,思路就是按照 \(topo\) 排序依次做任务即可。
可以先数一数有多少个分号,进而确定有多少个点。
然后再把所有点的名字处理出来,然后再处理点之间的依赖关系。
要注意的细节还是很多的。

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
struct node1{int y,next;}b[20001];
struct node2{int id,time;};
int sta[10001],lenb;
int a[10001],v[10001],rd[10001],js;
char name[10001][52];int lenname[10001];
char yl[200001][52];
int jsyl,begin1[10001],end1[10001];
int lenyl[200001];
int s[10001];
int rws;
bool operator <(struct node2 a1,struct node2 a2)
{
	if(a1.time!=a2.time)return a1.time>a2.time;
	else return strcmp(name[a1.id],name[a2.id])>0;
}
priority_queue <node2> q;
priority_queue <node2> d;
int lend1;
int read(){
	char ch;
	while(1){
		++js;
		while((ch=getchar())!=EOF&&ch!=':')name[js][lenname[js]++]=ch;
		name[js][lenname[js]]='\0';
		getchar();
		begin1[js]=jsyl+1;
		ch=0;
		while(ch!=']')
		{
			jsyl++;
			while((ch=getchar())!=EOF&&ch!=','&&ch!=']')yl[jsyl][lenyl[jsyl]++]=ch;
			yl[jsyl][lenyl[jsyl]]='\0';
			if(lenyl[jsyl]==0)jsyl--;
		}
		end1[js]=jsyl;
		getchar();
		int zf=0;
		if((ch=getchar())=='-')zf=1;else s[js]=(ch^48);
		while((ch=getchar())>='0'&&ch<='9')s[js]=(s[js]<<3)+(s[js]<<1)+(ch^48);
		if(zf)s[js]=(-s[js]);
		if(ch=='/')
		{
			int zf=0;
			if((ch=getchar())=='-')zf=1;else rws=(ch^48);
			while((ch=getchar())>='0'&&ch<='9')rws=(rws<<3)+(rws<<1)+(ch^48);
			if(zf)rws=(-rws);
			return 0;
		}
	}
}
int merge(int x,int y)
{
	b[++lenb].y=y;
	b[lenb].next=sta[x];
	sta[x]=lenb;
	rd[y]++;
	return 0;
}
int main()
{
	read();
	for(int i=1;i<=js;i++)
	{
		for(int j=begin1[i];j<=end1[i];j++)
		{
			for(int h=1;h<=js;h++)if(strcmp(name[h],yl[j])==0)merge(h,i);
		}
	}
	for(int i=1;i<=js;i++)if(rd[i]==0)
	{
		node2 p=(node2){i,0};
		q.push(p);
	}
	while(!q.empty()&&lend1<rws)
	{
			node2 p=q.top();
			p.time+=s[p.id];
			d.push(p);
			q.pop();
			lend1++;
		}
	int now=0;
	while(!d.empty())
	{
		now=d.top().time;
		while(!d.empty()&&d.top().time<=now)
		{
			node2 p=d.top();
			d.pop();
			lend1--;
			for(int i=sta[p.id];i;i=b[i].next)
			{
				rd[b[i].y]--;
				if(rd[b[i].y]==0)q.push((node2){b[i].y,now});
			}
			
		}
		while(!q.empty()&&lend1<rws)
		{
			node2 p=q.top();
			q.pop();
			lend1++;
			p.time=now+s[p.id];
			d.push(p);
		}
	}
	printf("%d",now);
	return 0;
}

模拟

【问题描述】

你是能看到第三题的 \(friends\) 呢。

——laekov

众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。

给定 \(N\) 个长度不超过 \(50\)\(01\) 字符串,你可以将其不断重复直到长度为 \(50!\)。现在要求输出从 \(1∼50!\) 中有多少个位置,在这个位置上所有字符串 \(1\) 的个数加起来等于 \(i\)

【输入格式】

第一行一个整数 \(N\) 代表字符串个数。

接下来 \(N\) 行每行一个字符串。

【输出格式】

输出 \(N+1\) 行,其中第 \(i\) 行代表 \(1\) 的个数为 \(i−1\) 的位置的个数对 \(10^9+7\) 取模之后的结果。

【样例输入】

1
1

【样例输出】

0

318608048

【数据规模与约定】

对于 \(20\%\) 的数据,\(N=1\)

对于 \(40\%\) 的数据,\(N≤10\)

对于另外 \(20\%\) 的数据,所有字符串长度均为质数。

对于另外 \(20\%\) 的数据,所有字符串长度小于等于 \(10\)

对于 \(100\%\) 的数据,\(1≤N≤100\),我觉得原来的第三题太简单了,所以临时换了这个题。

Solution

先理解一下题意:给出 \(n\) 个字符串,将它们都写至 \(50!\) 的长度,那么就产生了 \(50!\) 个位置,现在要问有多少个位置有 \(0\)\(1\),有多少位置有 \(1\)\(1...\),有多少位置有 \(n\)\(1\)

20pts

\(n=1\)
也就是说只有一个字符串,我们就考虑这一个字符串有多少个位置有 \(0\)\(1\),有多少个位置有 \(1\)\(1\)
假如给的字符串是 \(10010\),也就是说每五个位置就会出现 \(3\)\(0\) 和两个 \(1\),那么就有 \(3*\frac{50!}{5}\) 个位置是 \(0\),有 \(2*\frac{50!}{5}\) 个位置是 \(1\)

40pts

所有字符串长度小于等于 \(10\)
我们可以将所有的字符串先写到 \(2520\) 的长度\((1~10\)的最小公倍数\()\),这样每 \(2520\) 个就会出现一次循环,我们只需要暴力地求出前 \(2520\) 个中有 \(0\)\(1\) 的位置有几个,有 \(1\)\(1\) 的位置有几个...,然后再乘 \(\frac{50!}{2520}\) 就是对应的答案了。
貌似可以拿 \(60pts\)

80pts

所有字符串的长度均为质数。
只有这一部分分的做法与正解有关系。
假设我们有两个串 \(010\)\(10011\),我们还是先扩展到他们的最小公倍数的长度:
0 1 0 0 1 0 0 1 0 0 1 0 0 1 0
1 0 0 1 1 1 0 0 1 1 1 0 0 1 1

我们发现,第一个字符串的第一位分别与第二个字符串的每一位都对应了一次。
以此类推,第一个字符串的每一位都会与第二个字符串的每一位对应一次。
由于我们并不关心字符串本身长什么样,我们只关心有多少个位置出现了 \(0\)\(1\)...所以我们可以把字符串拆成以下形式:
\(010=2\)\(0+1\)\(1\)
\(10011=2\)\(0+3\)\(1\)
那么它们组合起来的结果是什么呢?类似于多项式乘法:
\((2\)\(0+1\)\(1)*(2\)\(0+3\)\(1)=4\)\(0+8\)\(1+3\)\(2\)
假如我们有两个长度为 \(5\) 的字符串:\(10011\)\(01011\),我们可以将它合并为:\(11022(1\)\(0+2\)\(1+2\)\(2)\)
这样我们就可以保证每个长度的字符串只有一个。
由于每个字符串的长度是互质的,所以我们将每个长度的字符串的表达式写出来,做一个乘法,问题就解决了。

100pts

由于此刻字符串的长度不互质,所以一个字符串的每一位不能和其他字符串的每一位都对应了。
例如有一个长度为 \(4\) 和一个长度为 \(6\) 的字符串,它们的最小公倍数是 \(12\),可以发现长度为 \(4\) 的字符串的每一位只与第二个字符串的 \(3\) 个位置对应。
不互质的情况能否转化为互质的情况呢?
我们先求出 \(\gcd(4,6)=2\),我们可以将长度为 \(4\) 的字符串分成 \(2\) 块,把长度为 \(6\) 的字符串分成 \(3\) 块,它们每一块的长度都为 \(2\)
会发现,现在并不是每一个位置会与下面进行对应,而是每一块会与下面的每一块进行对应。(显然两个字符串的块数互质,这样就转化成了上面部分分的情况)
由于块是整齐对应的,所以上面字符串的第一个块的第一个位置会与下面每个块的第一个位置对应,上面字符串的第一个块的第二个位置会与下面每个块的第二个位置对应...
所以我们可以对下面的字符串的每一个块的每一个位置算一遍答案,即将所有块的第一个位置算一遍答案,所有块的第二个位置算一遍答案...
还是举个例子,我们有一个长度为 \(4\) 的字符串 \(1001\) 和一个长度为 \(6\) 的字符串 \(010011\)
1 0 0 1 1 0 0 1 1 0 0 1
0 1 0 0 1 1 0 1 0 0 1 1

长度为 \(4\) 的字符串被分成了两块:\(10\)\(01\),长度为 \(6\) 的字符串被分成了三块:\(01,00\)\(11\)
根据上面讨论的结果,我们知道上面每一块的第一个位置 \(1\)\(0\),分别与下面每一块的第一个位置:\(0,0\)\(1\) 做一次对应。
贡献相当于:\(10×001=(1\)\(0+1\)\(1)*(2\)\(0+1\)\(1)=2\)\(0+3\)\(1+1\)\(2\)
同理:上面每一块的第二个位置 \(0\)\(1\),分别与下面每一块的第二个位置:\(1,0\)\(1\) 做一次对应:
贡献相当于:\(01×101=(1\)\(0+1\)\(1)*(1\)\(0+2\)\(1)=1\)\(0+3\)\(1+2\)\(2\)
答案就是每个位置的贡献之和。
但是当字符串一多,你每次两两合并的时候最大公因数会比较大,所以时间复杂度也会升高。
好像我们漏掉了一个非常非常重要的性质:字符串的长度小于等于 \(50\),没有这个性质还真的做不了。
由于 \(\sqrt{50}=7\),所以我们可以把 \(50\) 以内的质数分成两部分,一部分是小于等于 \(7\) 的,有:\(2,3,5,7\);还有一部分是大于 \(7\) 的,有:\(11,13,17,19,23,29,31,37,41,43,47\)
根据唯一分解定理可以知道:由于所有的字符串长度小于等于 \(50\),所以它们的长度都是在这里边找几个数乘起来。
考虑到我们的优化方式只有两种:
一种是你可以找到一个好的合并顺序,使得每次合并的最大公因数都不会太大;
另一种就是你可以令每次合并的最大公因数都是一样的,这样我们就可以一次把它们全都合并了。
那要怎么找呢?
发现 \(2\)\(50\) 以内的若干次幂最大是 \(32\)\(3\) 的话是 \(27\)\(5\) 的话是 \(25\)\(7\) 的话是 \(49\)
考虑一种情况,如果所有字符串的长度的质因子只有 \(2,3,5,7\) 的话,我们可以将它们都扩展为 \(32*27*25*49\) 的长度。
那如果不只含有 \(2,3,5,7\) 这四个因子呢?那它一定包含大于 \(7\) 的因子中的其中一个,且只能包含一个。
假设包含的这个质因子为 \(p\),那么它还可能包含一些小于 \(7\) 的质因子。
由于 \(p\) 最小是 \(11\),也就是说,我们只可能包含 \(2,3\) 这两个因子了,而且在 \(50\) 内最多只能将其扩展为 \(2p,3p,4p\)
由于 \(2,3,4\) 的最小公倍数是 \(12\),所以我们可以将其扩展为 \(12p\),这样一定保证是原字符串的倍数长度。
由于每个字符串都被扩展到了 \(12p\),由于 \(p\) 这个因子是上面字符串所没有的,那么它们的最大公因子一定是 \(12\)
所以我们把每个字符串的每 \(12\) 个位置分成一组,对于每个位置做一遍 \(dp\),一共 \(12\)\(dp\),那么这个题就做完了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;

#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}

const int maxn=1010;
const int mo=1000000007;
const int maxb=32*27*25*49;
const int prime[]={0,11,13,17,19,23,29,31,37,41,43,47,0};

int n,num[maxb+10],res[maxn][12],resx[maxn][12],ans[maxn],l[maxn];
//num[i]表示我们已经将当前字符串扩展至maxb的长度了,第i个位置是多少(0/1) 
//res[i][j]表示我们已经将前k个字符串合并了,块中位置为j中有几个i 
//resx是每次合并的辅助数组
//ans[i]表示合并完所有的字符串之后,有多少个位置有i个1 
char s[maxn][maxn];

int main()
{
	scanf("%d",&n);
	for (int a=1;a<=n;a++)
	{
		scanf("%s",s[a]+1);
		l[a]=strlen(s[a]+1);
	}
	for (int a=1;a<=n;a++)
		if (maxb%l[a]==0)                        //如果这个字符串的长度只含2,3,5,7这四个质因子 
			for (int b=1,c=1;b<=maxb;b++,c++)    //我们需要将其扩展至maxb的长度 
			{
				if (c>l[a]) c=1;                 
				if (s[a][c]=='1') num[b]++;      //统计每一个位置是0还是1 
			}
	for (int a=1;a<=maxb;a++)
		inc(res[num[a]][a%12],1);                //先把长度只含2,3,5,7质因子的字符串合并起来 
	for (int a=1;prime[a];a++)                   
	{
		int v=prime[a];                          //找长度含v质因子的字符串 
		memset(num,0,sizeof(num));
		for (int b=1;b<=n;b++)
			if (l[b]%v==0)
			{
				for (int c=1,d=1;c<=v*12;c++,d++)  //扩展至12*v 
				{
					if (d>l[b]) d=1;
					if (s[b][d]=='1') num[c]++; 
				}
			}
		memset(resx,0,sizeof(resx));
		for (int b=0;b<=n;b++)                    //合并的过程相当于dp,第一维枚举有几个1 
			for (int c=1;c<=v*12;c++)             //第二维枚举位置 
				inc(resx[b+num[c]][c%12],res[b][c%12]);  //这个位置能贡献出num[c]个1,进行转移 
		for (int b=0;b<=n;b++)
			for (int c=0;c<12;c++)
				res[b][c]=resx[b][c];
	}
	for (int a=0;a<=n;a++)
	{
		for (int b=0;b<12;b++)
			inc(ans[a],res[a][b]);            
		ans[a]%=mo;
	}
	int mul=1;
	for (int a=1;a<=50;a++)                        //算循环节 
	{
		if (a==32 || a==27 || a==25 || a==49) continue;
		bool able=true;
		for (int b=1;prime[b];b++)
			if (prime[b]==a) able=false;
		if (!able) continue;
		mul=1ll*mul*a%mo;
	}
	for (int a=0;a<=n;a++)
		printf("%d\n",(int)(1ll*ans[a]*mul%mo));

	return 0;
}

【问题描述】

你是能看到第四题的 \(friends\) 呢。

——laekov

众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。

定义函数 \(f\) 为计算序列 \(V\) 字的数量的函数。定义 \(V\) 字为 \(i<j<k,a_i>a_j,a_k>a_j\) 的三元组 \((i,j,k)\) 。现在给定 \(N\) 个数 \(a_1,a_2,⋯,a_N\),求

\(\sum_{l=1}^{n}\sum_{r=l}^{n}f(a_l,a_{l+1},⋯,a_r)\)

【输入格式】

第一行一个整数 \(N\)

接下来一行 \(N\) 个整数。

【输出格式】

一行一个整数代表答案对 \(10^9+7\) 取模之后的答案。

【样例输入】

5
2 3 1 4 5

【样例输出】

9

【数据范围与规定】

对于 \(30\%\) 的数据,\(N≤20\)

对于 \(60\%\) 的数据,\(N≤100\)

对于 \(80\%\) 的数据,\(N≤1000\)

对于 \(100\%\) 的数据,\(1≤N≤10^5,1≤a_i≤N\),我本来想出 \(W\) 的形状而非 \(V\) 的形状来着的。

Solution

60pts

我们按照题目中所说的去枚举 \(l,r,i,j,k\) 然后判断是否为三元组即可。
时间复杂度 \(O(n^5)\)

80pts

假如 \(a_i,a_j,a_k\) 能构成一个三元组,那么这个三元组包含于 \(i*(n-k+1)\) 个区间。
假如 \(j\) 的左边一共有 \(p\) 个数 \(a_{i_1},a_{i_2}...a_{i_p}\) 是大于 \(a_j\) 的,右边共有 \(q\) 个数 \(a_{k_1},a_{k_2}...a_{k_q}\) 是大于 \(a_j\) 的,我们固定 \(a_{i_1}\),则此时的贡献为 \(i_{1}*\sum_{b=1}^{q}(n-k_b+1)\),如果固定 \(a_{i_2}\) 贡献就是 \(i_{2}*\sum_{b=1}^{q}(n-k_b+1)\),那么总贡献就是 \(\sum_{c=1}^{p}i_c*\sum_{b=1}^{q}(n-k_b+1)\)
所以我们可以枚举 \(a_j\) 左边的数的符合条件的数的下标和,枚举右边符合条件的数的下标,算出答案。
时间复杂度 \(O(n^2)\)

100pts

我们可以用权值线段树或树状数组来求下标和。
时间复杂度 \(O(n\log{n})\)

#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
const ll N=1e5+5;
const ll M=1e9+7;
ll n,top;
ll a[N],L[N],R[N],sum[N<<2],times[N<<2],ans;
//权值线段树维护下标和sum和出现次数times, 
void update(ll node)
{
	sum[node]=sum[node<<1]+sum[node<<1|1];
	times[node]=times[node<<1]+times[node<<1|1];
}
void insert(ll node,ll l,ll r,ll x,ll k)
{
	if(l==r)
	{
		sum[node]+=k;                        //目前所有大小为l的数的下标和 
		times[node]++;                       //大小为l的数又出现了一次 
		return ;
	}
	ll mid=(l+r)>>1;
	if(x<=mid) insert(node<<1,l,mid,x,k);
	else insert(node<<1|1,mid+1,r,x,k);
	update(node);
}
ll query1(ll node,ll l,ll r,ll x,ll y)       //询问1返回下标和 
{
	if(l>r) return 0;
	if(x<=l&&r<=y) return sum[node];
	ll mid=(l+r)>>1;
	ll cnt=0;
	if(x<=mid) cnt+=query1(node<<1,l,mid,x,y);
	if(y>mid) cnt+=query1(node<<1|1,mid+1,r,x,y);
	return cnt; 
}
ll query2(ll node,ll l,ll r,ll x,ll y)       //询问2返回出现次数 
{
	if(l>r) return 0;
	if(x<=l&&r<=y) return times[node];
	ll mid=(l+r)>>1;
	ll cnt=0;
	if(x<=mid) cnt+=query2(node<<1,l,mid,x,y);
	if(y>mid) cnt+=query2(node<<1|1,mid+1,r,x,y);
	return cnt; 
}
int main()
{
	scanf("%d",&n);
	for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(ll i=1;i<=n;i++)               //求出每个数左边比它大的数的下标和 
	{
		L[i]=query1(1,1,n,a[i]+1,n);   //查询范围是a[i]+1~n 
		insert(1,1,n,a[i],i);   
	}
	memset(sum,0,sizeof(sum));
	memset(times,0,sizeof(times));
	for(ll i=n;i>=1;i--)               //求右边比它大的数,就要从右边开始枚举 
	{
		ll x=query1(1,1,n,a[i]+1,n);
		ll y=query2(1,1,n,a[i]+1,n);
		R[i]=y*(n+1)-x;
		insert(1,1,n,a[i],i);
	}
	for(ll i=1;i<=n;i++)
	    ans=(ans+L[i]*R[i]%M)%M;
	printf("%lld\n",ans%M);
	return 0; 
}
posted @ 2020-10-03 20:37  暗い之殇  阅读(229)  评论(0编辑  收藏  举报