省选模拟测试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;
}