洛谷 2 月月赛 II & EE Round 2
A 出言不逊 题目网址
题目描述
珂愛想出公开赛,但每次都被拒绝。
珂愛很生气,于是学会了出言不逊。
珂愛用一个字符串 S 存储了她想说的话,但这句话太逊了。为了出言不逊,珂愛要对字符串进行操作。每次操作,珂愛可以选择一个字符 c,若 c 在字符串 S 中出现了
x 次,则珂愛会将 x 个字符 c 补到 S 的尾部。
珂愛认为,这个字符串长度至少为 L 时,她才能出言不逊。珂愛想要知道,她至少需要操作多少次,才能让这个字符串的长度大于等于 L。
如果你不告诉珂愛,珂愛会对你出言不逊。
题目思路
纯模拟, 把当前出现次数最多的字母添到字符串尾部, 在判断就可以了。
注意统计完次数后可以用大根堆来取。
注意特判:如果最长的字母都是0的话, 就没必要继续了。
代码
string s;
unsigned long long L;
map<char, unsigned long long> mp;
priority_queue<unsigned long long>pq;
unsigned long long l;
int main()
{
cin >> s;
cin >> L;
l = s.size();
if(s.size() == L-1)
{
cout << 1 << endl;
return 0;
}
for(int i = 0; i < s.size(); i++)
{
mp[s[i]]++;
}
for(int i = '0'; i <= '9'; i++)
{
pq.push(mp[i]);
}
for(int i = 'A'; i <= 'z'; i++)
{
pq.push(mp[i]);
}
int ans = 0;
while(l < L)
{
ans++;
unsigned long long maxn = pq.top();
pq.pop();
if(l + maxn < l) break;
l += maxn;
maxn *= 2;
pq.push(maxn);
}
cout << ans << endl;
return 0;
}
B 谔运算 题目网址
题目描述
首先,CYJian 写出了一个长度为 n 的数列 a。
然后他灵光一动,写出了下面这个谔谔的式子:
∑
i
=
1
n
∑
j
=
1
n
∑
k
=
1
n
∑
l
=
1
n
(
a
i
o
r
a
j
)
x
o
r
(
a
k
a
n
s
a
l
)
\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n\sum_{l=1}^n (a_i\ or\ a_j)\ xor\ (a_k\ ans\ a_l)
i=1∑nj=1∑nk=1∑nl=1∑n(ai or aj) xor (ak ans al)
CYJian 觉得这个是一个谔运算的简单式子,摁计算器花了 114514s就算出来了答案。
为了证明你吊打 114514 个 CYJian,请你在
1s 内算出来这个式子的值吧。你只需要给出答案对 232取模的值即可。
题目思路
暴力肯定是不行的,于是我们选择先进行二进制拆分, 发现其实每一位对他都有贡献, 我们把四个数都进行二进制拆分, 发现每一位都有贡献, 我们可以看到为1就有贡献, 四个数式子唯一的情况共有10种。
如下:
a i a_i ai | 0 | 1 | 1 | 0 |
a j a_j aj | 1 | 0 | 1 | 0 |
a k a_k ak | 0 1 0 | 0 1 0 | 0 1 0 | 1 |
a l a_l al | 1 0 0 | 1 0 0 | 1 0 0 | 1 |
记住每位及它后面的的位都有两种情况。 所以第p位对答案的贡献还要乘上2p-1;
设n个数中位数为0的有x个, 1的有y个
则可得方案数为
2
x
3
y
+
6
x
2
y
2
+
2
x
y
3
=
f
[
p
]
2x^3y + 6x^2y^2 + 2 xy^3 = f[p]
2x3y+6x2y2+2xy3=f[p]
可能还不太懂, 解释一下:
第一项:三个0, 一个1, 所以字母为
x
3
y
x^3y
x3y, 参考上面的表格, 一共有两种情况, 所以系数为二;
以此类推:
最后乘上贡献在加起来。
最终答案就为:
∑
i
=
1
33
f
[
i
]
∗
2
i
−
1
\sum_{i=1}^{33} f[i] * 2^{i-1}
i=1∑33f[i]∗2i−1
别忘取模QAQ~~
代码
typedef long long ll;
using namespace std;
ll p = 1;
ll n;
ll a[500010];
ll x[500010], y[500010];
ll mi[50];
ll ans;
void cnt(ll k) // 计算每一位x与y的个数
{
for(register int i=1;i<=33;i++)
{
if(k%2==0) x[i]+=1;
else y[i]+=1;
k/=2;
}
}
ll f(int i) // 求f[i]
{
ll sum=0;
sum=(((2*x[i]*x[i])%p)*((x[i]*y[i])%p))%p;
sum=(sum+(((6*x[i]*x[i])%p)*((y[i]*y[i])%p))%p)%p;
sum=(sum+(((2*x[i]*y[i])%p)*((y[i]*y[i])%p))%p)%p;
return sum;
}
int main()
{
cin >> n;
for(int i = 1; i <= 32; i++)
{
mi[i] = p;
p *= 2;
}
a[33] = p;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
cnt(a[i]);
}
for(int i = 1; i <= 33; i++)
{
if(y[i] && x[i])
ans = (ans + (f(i) * mi[i])) % p;
}
cout << ans << endl;
return 0;
}
C 自然溢出啥事儿没有 题目网址
题目描述
给定一个整数n, 问有多少种长度为n的字符串, 满足这个字符串是一个程序片段。
具体定义如下:
单个分号*;* 是一个语句1
空串* * 是一个程序片段[^2]
如果字符串A是程序片段, 字符串B是语句, 则AB是程序片段。2
如果字符串A是程序片段, 则*{A}是语句块。3
如果字符串A是语句块, 则A是语句, []A和A都是函数。4
如果字符串A是函数, 则(A)是函数, A和A()都是值。5
如果字符串A是值, 则(A)是值, A;是语句。6
注意,A是B并不代表B是A*。
题目思路
这题是个dp;
设dp[i][0]是长度为i的字符串里语句的个数。
dp[i][1]是长度为i的字符串里代码片段的个数。
dp[i][2]是长度为i的字符串里语句块的个数。
dp[i][3]是长度为i的字符串里函数的个数
dp[i][4]是长度为i的字符串里值的个数.
下面我们来逐句地解释:(见文章末尾)
代码
unsigned long long n;
unsigned long long dp[100010][5];
int main()
{
cin >> n;
dp[0][1] = dp[1][1] = dp[1][0] = 1;
for(int i = 2; i <= n; i++)
{
dp[i][3] = dp[i-2][3] + dp[i-2][2];
dp[i][2] = dp[i-2][1];
if(i >= 4)
{
dp[i][3] += dp[i-4][2];
}
dp[i][0] = dp[i][2] + dp[i-1][4];
dp[i][4] = dp[i][3] + dp[i-2][4];
for(int j = 0; j < i; j++)
{
dp[i][1] += dp[j][1] * dp[i - j][0];
}
}
cout << dp[n][1] << endl;
return 0;
}
D相同的数字。题目网址
题目描述
每天早上在黑板上会写有 n 个固定的数字,但是这些数字太无序了,所以每天晚上兔子想把他们变成相同的数字。
有两种操作 :
1.选择一个下标k, 兔子把
a
k
a_k
ak 变成
a
k
+
1
a_{k+1}
ak+1, 花费的时间为
c
1
c_1
c1。
2.选择一个下标k, 把
a
k
a_k
ak替换成大于
a
k
a_k
ak的最小整数, 花费的时间为
c
2
c_2
c2
兔子很懒,所以他不想花费太多的时间,你需要帮他计算出将所有数变相同的最小时间。
总共会有 q 天。兔子每天的状态不同,所以每一天会有不同的
c
1
c_1
c1和
c
2
c_2
c2:。但是黑板上的数不会变。
第一天花费的时间当然会影响第二天的状态。每天真实的
c
1
=
c
1
′
⊕
(
T
×
(
l
a
s
t
a
n
s
m
o
d
2
17
)
)
c_1 = c'_1\oplus (T \times (lastans \bmod 2^{17}))
c1=c1′⊕(T×(lastansmod217)).
c
2
=
c
1
′
⊕
(
T
×
(
l
a
s
t
a
n
s
m
o
d
2
17
)
)
c_2 = c'_1\oplus (T \times (lastans \bmod 2^{17}))
c2=c1′⊕(T×(lastansmod217)).
其中
⊕
\oplus
⊕为
x
o
r
xor
xor运,
l
a
s
t
a
n
s
lastans
lastans为上一次的答案, 最初
l
a
s
t
a
n
s
lastans
lastans = 0;
题目思路。
其实我在考场上想出思路来了, 可是由于码力不行QAQ, 所以没有模拟出来, 加上对于T3dp鬼题的打击, 我太菜了~~~~。
其实这个题有很大的切入点, 不想T3, 看起来无从下手。
这个题, 说实话, 真的, 从哪开始想都可以, 我们可以从终点考虑,或者从过程考虑, 假如丛终点, 我们会发现, 最终整个数列变成的数要不是数列中的最大值, 要么就是大于最大值的最小质数。
再考虑怎么跳, 显然要让花费的时间最小, 肯定要有策略:
首先两个质数间最大不超过154(因为第二种变化其实就是跳到下一个质数), 不信可以打表, 上限1e7;
然后设质数的间距为t, 下面就是用比大小来择优,由于考虑到后面的优化 我把一种情况的不等式列出来, 就是选c2的:
c
⋅
t
≥
t
2
c \ \cdot \ t\ \ge \ t2
c ⋅ t ≥ t2 解得
t
≥
c
1
/
c
2
t \geq c1 / c2
t≥c1/c2
由于是询问, 所以对于某次询问, 最好O(1)就能算出, 所以:
将所有质数跳的次数和跳的距离维护后缀和,然后根据c2/c1的计算找到对应的位置,o(1)即可计算。
所以问题就变为如何统计这些数在质数上的跳跃次数和+1跳跃次数。这里的细节比较多。
因为n,q都比较大,所以应该预处理n,然后再线性处理q。
欧了欧了。。。
附加:最近又新改了一下, 细节都放到代码里了。
总之, 临界值就是c1/c2如果比这个小, 就一步一步跳, 否则就跳质数
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e7 + 160;
const int mod = (1<<17);
bool isnp[MAXN];
int p[MAXN],pcnt=0;
ll sump[MAXN];//sump[]存储到当前数为值出现的质数的个数,其下一个质数就是p[sum[]+1]。
ll n,q,T,a[MAXN],c[2][200],maxa,maxp,ans,lans=0,sumstep[2],behc[2][200],behs[2][200];
//c【i】[j】距离j的次数有几次
//sumstep是跳一步有几次
//beh 后缀
void prim(int N) {
isnp[0]=isnp[1]=1;
for(int i=2; i<N; ++i) {
if(!isnp[i]) {
p[++pcnt]=i;
}
sump[i]=pcnt;
for(int j=1; j<=pcnt; ++j) {
if((ll)i*p[j]>=N) break;
isnp[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
}
//求这些数到终点x的的值终点有两种可能,到n;如果n不是质数,到n对应的下一个质数
void count(int flag,int x){//计算出:1.需要跳多少个1步sumstep;2桶计数c[]:跳到对应的质数间距计数
sumstep[flag]=0;
if(flag==0)//这些数要跳到一个合数,只能+1来跳。
sumstep[flag]+=(n-1)*(a[n]-p[sump[a[n]]]),a[n]=p[sump[a[n]]];
else if(isnp[a[n]]){//可以通过+1跳到这个质数,也可以通过下一个质数跳到。
sumstep[flag]+=p[sump[a[n]]+1]-a[n];
c[flag][sumstep[flag]]++;
a[n]=p[sump[a[n]]+1];
}
int j=sump[a[n]];
for(int i=n;i>1;i--){
sumstep[flag]+=(a[n]-a[i-1]); //+1来挑
while(p[j]>a[i-1]&&p[j-1]>=a[i-1]&&j){
c[flag][p[j]-p[j-1]]+=(i-1);//从质数跳到质数
j--;
}
if(isnp[a[i-1]])c[flag][p[j]-a[i-1]]++; //第一步可能是合数跳到质数
}
//因为质数跳的距离越大用c2越合算,维护c[t]的后缀和,和所代替步数t*c[t]的后缀和
for(int i=161;i>=1;i--){
behc[flag][i]=behc[flag][i+1]+c[flag][i];
behs[flag][i]=behs[flag][i+1]+i*c[flag][i];
}
a[n]=x;
}
ll work(ll c1,ll c2){
c1=c1^(T*lans),c2=c2^(T*lans);
ll t=ceil(1.0*c2/c1);
if(t>160)t=160;
ll sum0=(sumstep[0]-behs[0][t])*c1+behc[0][t]*c2;
ll sum1=(sumstep[1]-behs[1][t])*c1+behc[1][t]*c2;
return min(sum0,sum1);
}
int main(void) {
// freopen("in.txt","r",stdin);
ll c1,c2;
scanf("%lld%lld%lld",&n,&q,&T);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
sort(a+1,a+1+n);
prim(a[n]+158) ;
count(0,a[n]);
count(1,a[n]);
for(int i=1;i<=q;i++){
scanf("%lld%lld",&c1,&c2);
ans=work(c1,c2);
lans=ans%mod;
printf("%lld\n",ans);
}
}
小结
最近状态不好, 比赛也没打好, g估计在家里憋的老想摸鱼, 哎~~
dp[1][0] = 1;
[^2] :dp[0][1] = 1; ↩︎dp[1][1]= 1; dp[i][1] = ∑ j = 0 i − 1 d p [ j ] [ 1 ] ∗ d [ i − j ] [ 0 ] \sum_{j=0}^{i-1} dp[j][1] * d[i-j][0] ∑j=0i−1dp[j][1]∗d[i−j][0]
解释, du对于每个字符串, 代码片段和语句都有不同的长度, 假如代码片段长度和语句长度都已确定的话, 那么当前长度的代码片段个数和语句个数每个都一一对应, 因此要相乘。 ↩︎dp[i][2] = dp[i-2][1]; ↩︎
dp[i][0] = dp[i][2], dp[i-2][2] + dp[i-4][2] = dp[i][3] ↩︎
dp[i][3] +=dp[i-2][3], dp[i][4] = dp[i][3] + dp[i-2][3]; ↩︎
dp[i][4] += dp[i-2][4]; dp[i][0] += dp[i][4];
注意重复d[i][4] += dp[i-2][4]; ↩︎