省选模拟测试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),并且所有操作均合法(即 (0leq x,y<i,0leq l < rleq Len(S_x))

数据范围:(nleq 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
",dfs(n,0,len[n]-1));
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 Different

题意描述

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

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

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

数据范围:(sum nleq 3cdot 10^5,1leq dleq 10^6, 0leq a_ileq 10^{11})

solution

动态规划。

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

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

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

转移则有:(f_{i,j} = min(f_{i-1,k} + |a_i-j|) kin{{- 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
",ans);
	}
	return 0;
} 

T3 Circle

题意描述

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

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

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

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

数据范围: (n,mleq 10^6)

solution

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

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

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

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

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

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

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

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

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

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

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

化简一下就可以得到:(f[n][0] = f[n-1][0] + {1over 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
",g[m]);
	fclose(stdin); fclose(stdout);
	return 0;
}
原文地址:https://www.cnblogs.com/genshy/p/14577205.html