来自学长的快乐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,b≤1e7(有人写程序亲测),以下式子貌似成立。
(
a
&
b
)
+
(
a
∣
b
)
=
a
+
b
(a\&b)+(a|b)=a+b
(a&b)+(a∣b)=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=2∗l=1∑nr=l∑nseed(l−1)∗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∗(m−1)n−1。两个数都可以用快速幂轻松算出来,最后相减即可。
代码
#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;
}