省选模拟测试14

\(T1\) 题意理解错了,暴力分就打挂了。

\(T2\) 打了个 \(O(n^2)\) 的暴力,一些边界问题没注意到就写错了。

\(T3\) 看题面以为是个组合计数的题,和题解的思路一点也不一样。

T1 String

题意描述

你有一个字符串 \(S_0\),想要用它造出共 \(n\) 个新字符串 \(S_{0},\cdots,S_{n-1}\)

你会按顺序进行 \(n-1\) 次操作,造出新字符串。第 \(i\) 次操作有两种方法:

  • S x l r 截取 \(S_x\)\([l,r)\) 子串,即第 \(l\) 到第 \(r-1\) 个字符作为 \(S_i\)
  • A x y\(S_x\)\(S_y\) 拼接作为 \(S_i\)

最后,你会关心 \(S_{n-1}\) 长什么样,因此请输出 \(S_{n-1}\) 所有字符的 ASCII 码之和,结果对 \(10^9+7\) 取模。

本题中字符串下标均从 \(0\) 开始编号,且保证所有字符串串长不超过 \(2^{63}-1\),并且所有操作均合法(即 \(0\leq x,y<i,0\leq l < r\leq Len(S_x)\)

数据范围:\(n\leq 2000,len(S)\leq 2000\)

solution

记忆化搜索。

暴力递归能有 \(60pts\), 加上记忆化搜索就过了。

注意字符串长度要开 \(\ unsigned \ long \ long\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
#define int long long
#define mp make_pair
const int p = 1e9+7;
const int N = 5010;
int n,len[N],num[N],sum[N];
string s[N];
struct node
{
    int opt,x,l,r;
}q[N];
struct QAQ
{
	int x,l,r;
	QAQ(){}
	QAQ(int a,int b,int c){x = a,l = b, r = c;}
};
bool operator < (QAQ a,QAQ b)
{
	if(a.x == b.x) 
	{
		if(a.l == b.l) return a.r < b.r;
		else return a.l < b.l;
	}
	return a.x < b.x;
}
map<QAQ,int> has;
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
int dfs(int now,int l,int r)
{
    if(has[QAQ(now,l,r)]) return has[QAQ(now,l,r)];
    if(now == 1)
    {
    	if(l == 0) return has[QAQ(now,l,r)] = sum[r];
    	else return has[QAQ(now,l,r)] = sum[r]-sum[l-1];
    }
    if(q[now].opt == 1) 
    {
        int to = q[now].x;
        return has[QAQ(now,l,r)] = dfs(to,q[now].l+l,q[now].l+r);
    }
    if(q[now].opt == 2)
    {
        int son1 = q[now].l;
        int son2 = q[now].r;
        if(r < len[son1]) return has[QAQ(now,l,r)] = dfs(son1,l,r);
        else if(l >= len[son1]) return has[QAQ(now,l,r)] = dfs(son2,l-len[son1],r-len[son1]);
        else return has[QAQ(now,l,r)] = dfs(son1,l,len[son1]-1) + dfs(son2,0,r-len[son1]); 
    }
}
signed main()
{
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
    n = read(); cin>>s[1];
    len[1] = s[1].length(); sum[0] = (int) s[1][0]; 
    for(int i = 1; i < len[1]; i++) sum[i] = sum[i-1] + (int)s[1][i];
    for(int i = 2; i <= n; i++)
    {
        char ch; cin>>ch;
        if(ch == 'S')
        {
            q[i].opt = 1;
            q[i].x = read() + 1;
            q[i].l = read();
            q[i].r = read();
            len[i] = (q[i].r-q[i].l);
        }
        else
        {
            q[i].opt = 2;
            q[i].l = read() + 1;
            q[i].r = read() + 1;
            len[i] = len[q[i].l] + len[q[i].r];
        }
    }
    printf("%lld\n",dfs(n,0,len[n]-1));
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 Different

题意描述

你有 \(n\) 株植物,高度为 \(a_i\)。你希望让它们的高度看起来不太相同,具体来说:任意两株高度差的绝对值不小于 \(d\)

每次你可以选择一株植物,将其拔高或压低 \(1\),这花费你 \(1\) 的力气,这个操作可以进行任意多次。注意,植物高度不能是负的,即你需要保证任意时刻 \(a_i\geq 0\)

你最少需要使用多少力气,才能使得植物高度看起来不太相同呢?

数据范围:\(\sum n\leq 3\cdot 10^5,1\leq d\leq 10^6, 0\leq a_i\leq 10^{11}\)

solution

动态规划。

首先我们先排一下序,令 \(a_i = a_i-(i-1)\times d\)

那么我们的问题就转化为最小代价使序列单调递增。

\(f_{i,j}\) 表示处理完前 \(i\) 柱植物且第 \(i\) 柱植物的高度为 \(j\) 的最小代价是多少。

转移则有:\(f_{i,j} = min(f_{i-1,k} + |a_i-j|) k\in{\{- \infty,j\}}\)

直接转移是 \(O(n^2)\) 的。

考虑维护一下这个函数的图像,不难发现每次转移都是取前缀最大值在加一个平移的绝对值函数。

由于斜率连续,所以我们考虑那堆来维护一下拐点。

处理的时候要特判一下 \(a_i<0\) 以及 \(a_i = 0\) 的情况。

最后根据斜率就可以算出 \(f(0)\) 的值。

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#define int long long
using namespace std;
const int N=3e5+7;
int T,n,d,cnt,res;
int a[N],s[N];
priority_queue<int> q;
signed main()
{
    scanf("%lld",&T);
    while(T--)
    {
		res=0;
		scanf("%lld%lld",&n,&d);
		for(int i = 1; i <= n; i++) scanf("%lld",&a[i]);
		sort(a+1,a+n+1);
		for(int i = 1; i <= n; i++)
		{
			int x = max(0ll,a[i]-(i-1)*d);
			q.push(x); q.push(x); q.pop();
		}
		for(int i = 1; i <= n; i++) res += abs(a[i]-(i-1)*d);
		cnt=0;
		while(q.size()) s[++cnt] = q.top(),q.pop(); s[++cnt]=0;
		for(int i = 1; i < cnt; i++) res -= i * (s[i]-s[i+1]);
		printf("%lld\n",ans);
	}
	return 0;
} 

T3 Circle

题意描述

你有 \(2n+m\) 根绳子,每根绳子两端染有黑、白两种颜色。

其中 \(n\) 根绳子两端都是黑色; \(n\) 根绳子两端都是白色; \(m\) 根绳子一段黑色另一端白色。

现在绳子一端为黑色和白色的线头分别有 \(2n+m\) 个,你想要把黑色和白色一端的线头随机配对(共 \((2n+m)!\) 种方法),每对配对的线头用胶水拼接起来。可以发现,这样所有绳子一定会绕成若干个环。

你想要求出环的个数期望是多少,结果对 \(10^9+7\) 取模。

数据范围: \(n,m\leq 10^6\)

solution

期望 \(dp\) 。这题的思路好奇妙啊。

考虑设 \(f[i][j]\) 表示有 \(i\) 个绳子两端都是黑色 \(i\) 根两端都为白色,\(j\) 根绳子一端黑色一端白色的环的个数的期望。

转移就有:\(f[n][m] = f[n][m-1] + {1\over 2n+m}\)

解释一下,考虑取出一条一端为黑色一端为白色的线段去和 \(2n+m\) 条线段匹配。

为了画图方便(偷懒)图上的红色表示端点为黑色,蓝色则表示端点为白色。

不难发现只有这条线段和自己匹配的时候环的数量才会加一。其余情况和图上画的那个是一样的。

因为我们取出来了一条一段为黑色一段为白色的线段,所以剩下的两种线段的个数为分别为 \(n,m-1\).

在乘上概率就得到了我们的递推式 \(f[n][m] = f[n][m-1] + {1\over 2n+m}\)

现在只要知道 \(f[n][0]\) 我们就可以递推的求出 \(f[n][m]\) 了。

对于 \(f[n][0]\) 又转移 \(f[n][0] = f[n-1][1] = f[n-1][0] + {1\over 2n-1}\)

考虑拿出一条两端都为黑色的线段去和其他线段匹配,显然这条线段只能和两端都为白色的匹配,从而得到一端为白色一端为黑色的线段。匹配完之后两种线段的数量分别为:\(n-1,1\) 。又因为在这个匹配的过程中环的数量不会增加,所以就有 \(f[n][0] = f[n-1][1]\)

化简一下就可以得到:\(f[n][0] = f[n-1][0] + {1\over 2n-1}\)

有了这两个递推式,我们就可以在 \(O(n+m)\) 的时间内递推出 \(f[n][m]\) 了。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 3e6+10;
const int p = 1e9+7;
int n,m,ans,f[N],g[N];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
int ksm(int a,int b)
{
	int res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}
signed main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	n = read(); m = read();
	for(int i = 1; i <= n; i++) f[i] = (f[i-1] + ksm(2*i-1,p-2)) % p;
	g[0] = f[n];
	for(int i = 1; i <= m; i++) g[i] = (g[i-1] + ksm(2*n+i,p-2)) % p;
	printf("%lld\n",g[m]);
	fclose(stdin); fclose(stdout);
	return 0;
}
posted @ 2021-03-25 14:06  genshy  阅读(47)  评论(0编辑  收藏  举报