洛谷 2 月月赛 II & EE Round 2

洛谷 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=1nj=1nk=1nl=1n(ai or aj) xor (ak ans al)
CYJian 觉得这个是一个谔运算的简单式子,摁计算器花了 114514s就算出来了答案。
为了证明你吊打 114514 个 CYJian,请你在
1s 内算出来这个式子的值吧。你只需要给出答案对 232取模的值即可。

题目思路

暴力肯定是不行的,于是我们选择先进行二进制拆分, 发现其实每一位对他都有贡献, 我们把四个数都进行二进制拆分, 发现每一位都有贡献, 我们可以看到为1就有贡献, 四个数式子唯一的情况共有10种。
如下:

a i a_i ai0110
a j a_j aj1010
a k a_k ak0 1 00 1 00 1 01
a l a_l al1 0 01 0 01 0 01

记住每位及它后面的的位都有两种情况。 所以第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=133f[i]2i1
别忘取模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语句[]AA都是函数4
如果字符串
A函数, 则(A)是函数, AA()都是5
如果字符串
A, 则(A)A;是语句。6
注意,A
B
并不代表
BA*。

题目思路

这题是个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 tc1/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估计在家里憋的老想摸鱼, 哎~~


  1. dp[1][0] = 1;
    [^2] :dp[0][1] = 1; ↩︎

  2. 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=0i1dp[j][1]d[ij][0]
    解释, du对于每个字符串, 代码片段和语句都有不同的长度, 假如代码片段长度和语句长度都已确定的话, 那么当前长度的代码片段个数和语句个数每个都一一对应, 因此要相乘。 ↩︎

  3. dp[i][2] = dp[i-2][1]; ↩︎

  4. dp[i][0] = dp[i][2], dp[i-2][2] + dp[i-4][2] = dp[i][3] ↩︎

  5. dp[i][3] +=dp[i-2][3], dp[i][4] = dp[i][3] + dp[i-2][3]; ↩︎

  6. dp[i][4] += dp[i-2][4]; dp[i][0] += dp[i][4];
    注意重复d[i][4] += dp[i-2][4]; ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值