来自学长的快乐AK题——Day1


A——快乐区间动规

Description

定义操作a(X)b= ((a&b) + (a|b))>>1

给出n个数ai

每次用操作合并任意相邻ai
求进行n-1次操作以后可能得到的最终结果

所有结果从小到大输出

思路

考虑区间DP,设fi,j,k表示区间i~j能否通过合并得到数字k,为1则能,为0则否。

考虑如何转移。用区间DP的老套路,枚举分割点g,将区间i~j分割为区间i~g和区间g+1~j,将两个小区间的状态合并得到大区间的状态。
对于任意状态fi,g,p和fg+1,j,q,若两者都为1,则fi,j,((p&q)+(p|q))>>1也为1。p,q暴力枚举即可。

有趣的小结论

起码对于 a , b ≤ 1 e 7 a,b\leq1e7 a,b1e7(有人写程序亲测),以下式子貌似成立。
( a & b ) + ( a ∣ b ) = a + b (a\&b)+(a|b)=a+b (a&b)+(ab)=a+b

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200;
int n,f[N][N][8];
int main()
{
	scanf("%d",&n);
	for(int i=1,k;i<=n;i++)
	scanf("%d",&k),f[i][i][k]=1;//初始化 
	for(int len=2;len<=n;len++)
	for(int i=1;i+len-1<=n;i++)
	{
		int j=i+len-1;
		for(int k=i;k<j;k++)
		//暴力枚举小区间的状态 
		for(int p=0;p<8;p++)
		for(int q=0;q<8;q++)
		if(f[i][k][p]&&f[k+1][j][q])
		f[i][j][(p+q)>>1]=1;//合并得到大区间的状态 
	}
	for(int k=0;k<8;k++)
	if(f[1][n][k]) printf("%d ",k);
	return 0;
}

B——蜜汁做法

Description

在这里插入图片描述
在这里插入图片描述

思路

对于一个矩阵,

左上角为[x1,y1],右下角为[x2,y2]
它的权值计算为

ax1*(by1+…+by2) +

ax2*(by1+…+by2)

即(ax1+…+ax2)*(by1+…+by2)

发现x跟y没有必然的关系

可以分开处理

枚举a的可能区间[x1,x2],预处理所有的by区间和

对于一个限制[L,R]

若x选定为[x1,x2],则(L/suma[x1,x2],R/suma[x1,x2])

为by区间和可以选择的范围(注意考虑小数取整)

对于这个范围我就可以在预处理的by区间在排序后进行二分,找到符合的区间个数

时间复杂度O(n2log2(m2))

(ctl+c,ctl+v的教练的题解QAQ。没办法,写得太好了)

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2*1e3;
ll n,m,l,r,cnt,ans,a[N],b[N],s[N*N],sum;
int main()
{
	scanf("%lld%lld%lld%lld",&n,&m,&l,&r);
	//前缀和维护a,b数组的任意区间和 
	for(int i=1,k;i<=n;i++)
	scanf("%d",&k),a[i]=a[i-1]+k;
	for(int i=1,k;i<=m;i++)
	scanf("%d",&k),b[i]=b[i-1]+k;
	//预处理b数组的所有区间和并排序 
	for(int i=1;i<=m;i++)
	for(int j=i;j<=m;j++)
	s[++cnt]=b[j]-b[i-1];
	sort(s+1,s+cnt+1);
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	{
		sum=a[j]-a[i-1];//对于每个确定的a数组区间 
		ll p=l/sum,q=r/sum;//得到b数组区间和的上界、下界 
		if(l%sum) p++;
		//在s数组中二分得到上、下界的位置 
		ll x=upper_bound(s+1,s+cnt+1,q)-s;
		ll y=lower_bound(s+1,s+cnt+1,p)-s;
		ans+=x-y;//累计满足上下界的答案 
	}
	printf("%lld",ans);
	return 0;
}
}

C——模拟+维护中位数

Description

给出一个长度为n的排列ai以及seed,求

a n s = 2 ∗ ∑ l = 1 n ∑ r = l n s e e d ( l − 1 ) ∗ n + r m i d ( l , r ) ans=2*\sum _{l=1}^n \sum _{r=l}^n seed^{(l-1)*n+r}mid(l,r) ans=2l=1nr=lnseed(l1)n+rmid(l,r)

结果对1000000007取模。

mid(l,r)指al,al+1,…,ar-1,ar 的中位数。

思路

a是一个全排列,n<=10^4

如果进行枚举的话复杂度是O(n^2),但是对中位数的计算要做到O(1)不然会超时

考虑怎么维护中位数

对于原序列a1,…,an整体维护一个链表S,因为原序列是n的全排列,所以链表中初始时数i 的pre为i-1,next为i+1

此时记中位数为num1,num2

若为奇数序列则取中间数,num1=num2,

若为偶数序列则取中间大的两个,num1<num2

从后往前删掉了一个数时更新S:

考虑Sr+1 -> Sr, 删掉了ar,对中位数的影响

①奇数序列(num1=num2=t)变成偶数序列,

若ar<t,num2应该后移,num1不变

若ar>t,num1应该前移,num2不变

若ar=t,num1前移,num2后移

②偶数序列变成奇数序列(num1=num2),

若ar<=num1,显然num1应该后移,num2不变

若ar>=num2,显然num2应该前移,num1不变

每次维护就是进行ar,num1,num2的关系比较,转换

对于每个维护的Sr而言,因为Sr维护的是a1,…ar的中位数,我现在对于一个r而言,

我需要知道[1,r]的中位数,[2,r]的中位数,…,[r,r]的中位数

那么令T=Sr,

T[1,r]->T[2,r]->T[3,r]->…->T[r,r] 同理

每次从左端的a开始删,

每删掉一个就比较T中维护的num1,num2与aL的关系即可

对于seed^n用快速幂预处理就好

因为每次中位数的维护是O(1)的

所以时间复杂度是O(n^2)

(没错,又是教练的题解QAQ)

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007,N=1e4+5;
struct link
{
	int pre[N],nxt[N];
	void del(int x)
	{
		nxt[pre[x]]=nxt[x];
		pre[nxt[x]]=pre[x];
	};
}p,q;
int n,seed,a[N];
ll num,ans;
ll ksm(ll a,ll p)
{
	ll pro=1;
	while(p)
	{
		if(p&1) pro=(pro*a)%mod;
		p>>=1;
		a=(a*a)%mod;
	}
	return pro;
}
int main()
{
	scanf("%d%d",&n,&seed);
	num=ksm(seed,n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	p.pre[i]=i-1,p.nxt[i]=i+1;
	int mid1=(n+1)>>1,mid2=(n>>1)+1; 
	for(int i=n;i>=1;i--)//i指向右边界,即i为r 
	{
		int mida=mid1,midb=mid2;
		ll temp=ksm(seed,i);
		q=p;
		for(int j=1;j<=i;j++)//j指向左边界,即j为l 
		{
			ans=(ans+temp*(mida+midb)%mod)%mod;
			//处理左边界右移后区间的中位数 
			if(mida==midb)//如果是奇数序列 
			{	
				if(a[j]<=midb) midb=q.nxt[midb];//被删除的数在左边,右指针右移 
				if(a[j]>=mida) mida=q.pre[mida];//被删除的数在右边,左指针左移 
				//不能加else,因为可能被删除的就是中位数,mida和midb都需要调位 
			}
			else//如果是偶数序列
			{
				if(a[j]<=mida) mida=q.nxt[mida];//被删除的数在左边,左指针右移 
				if(a[j]>=midb) midb=q.pre[midb];//被删除的数在右边,右指针左移 
			}
			q.del(a[j]);
			temp=(temp*num)%mod;//将seed的指数拆分开来计算 
		}
		//处理右边界左移后区间的中位数 
		if(mid1==mid2)
		{
			if(a[i]<=mid2) mid2=p.nxt[mid2];
			if(a[i]>=mid1) mid1=p.pre[mid1];
		}
		else
		{
			if(a[i]<=mid1) mid1=p.nxt[mid1];
			if(a[i]>=mid2) mid2=p.pre[mid2];
		}
		p.del(a[i]);
	}
	printf("%lld",ans);
	return 0;
}

D——简单的容斥+快速幂

Description

有n个人依次排队打饭,有m种饭菜可以选择,每个人可能选择其中一种,如果相邻排队的人打的菜一样,那么就会影响彼此吃饭的心情,求这个队伍中有人被影响心情的状态数,对100003取余。

思路

直接考虑有人被影响心情的方案数显然有些困难,但容易发现总方案数和没有人被影响心情的方案数更容易计算,我们就可以考虑容斥,用总方案数减去不合法的方案数,得到合法的方案数。

考虑队伍里的n个人,每个人都有m种选择,显然总方案数为 m n m^n mn
考虑没有人被影响心情的方案数,第一个人可以随意选择m种,而第二个人不能与第一个人相同,因此只能选m-1种,而第三个人只需与第二个人不同,因此能选择m-1种,以此类推,方案数显然为 m ∗ ( m − 1 ) n − 1 m*(m-1)^{n-1} m(m1)n1。两个数都可以用快速幂轻松算出来,最后相减即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=100003;
ll n,m;
ll ksm(ll a,ll p)
{
	ll pro=1;
	while(p)
	{
		if(p&1) pro=(pro*a)%mod;
		p>>=1;
		a=(a*a)%mod;
	}
	return pro;
}
int main()
{
	scanf("%lld%lld",&m,&n);
	ll tot=ksm(m,n),fal=(ksm(m-1,n-1)*m)%mod;
	ll tru=(tot-fal+mod)%mod;//因为减法可能减出负数,因此要加上一个mod 
	printf("%lld",tru);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值