由于本蒟蒻的数学太弱啦!所以在联赛前有必要刷一刷洛谷的“提高试炼场-其他数学问题”。
难度评级是根据本蒟蒻的感受来写的,满分是五颗星。
难度评级: ※※※※
题目分析:
我们用一个m位二进制数表示后m个花圃的状态,1表示为C,0表示为P。
那么令t(i,j)表示由状态i转移到状态j的方案数(i和j都合法,即1的个数不超过k)。所谓转移,是指如果i表示第1~第m个花圃的状态,那么j代表第2~第m+1个花圃的状态。
一开始两个合法转移状态之间t(i,j)=1,合法转移状态为j右移一位,在左边添加一个1(或不添加)为i,这个可以dfs搞出来。
然后我们会发现i转移到j,可以看作i先转移到一个状态k,再由k转移到j,这是一个满足结合律的类似floyed的式子,可以用矩阵快速幂来搞。
由于所有的花圃是一个环,所以第1~m个花圃就是第n+1~n+m个花圃,所以我们求的答案就是一个合法状态转移n次,转移回原状态的方案数之和。
代码:
#include
#include
#include
#include
using namespace std;
#define LL long long
LL n,m,k,lim,mod=1000000007,ans;
struct node{LL t[64][64];}re,x;
int ok[64],bin[6];
void work(int zt,int num) {//处理合法转移
ok[zt]=1;int kl=zt>>1;
x.t[kl][zt]=1;
if(num==k&&!(zt&1)) return;//可转移状态也必须合法
x.t[kl+bin[m]][zt]=1;
}
void dfs(int x,int num,int zt) {//处理合法状态
if(x==m+1) {work(zt,num);return;}
dfs(x+1,num,zt);
if(num1,num+1,zt|bin[x]);
}
node operator * (node a,node b) {//重载运算符
int i,j,k;node c;
for(i=0;i<=lim;++i)
for(j=0;j<=lim;++j) {
c.t[i][j]=0;
for(k=0;k<=lim;++k)
c.t[i][j]+=a.t[i][k]*b.t[k][j]%mod,c.t[i][j]%=mod;
}
return c;
}
void ksm() {//快速幂正文
int bj=0;
while(n) {
if(n&1) {
if(!bj) re=x,bj=1;
else re=re*x;
}
x=x*x;n>>=1;
}
}
int main()
{
bin[1]=1;for(int i=2;i<=5;++i) bin[i]=bin[i-1]<<1;
scanf("%lld%lld%lld",&n,&m,&k);
lim=(1<1;dfs(1,0,0);ksm();
for(int i=0;i<=lim;++i) if(ok[i]) ans+=re.t[i][i],ans%=mod;
//统计答案
printf("%lld",ans);
return 0;
}
难度评级: ※※※
题目分析:
首先,逆序对越多,字典序越大(如果不明白打个表看看吧,我也没法严谨证明)
考虑”1”的位置。
假如1后面可以产生最多逆序对个数((n-1)*(n-2)/2),如果这个数要大于等于m的话,为了追求字典序最小,1放在第一个是没有问题的。但是如果这个数小于m的话,”1”务必要对逆序对个数做出一点贡献。
可以发现,不管1放在哪里,和1无关的逆序对个数最多都是m-(与1有关的逆序对个数)个。为了让字典序最小,也就是要让第一个数最小,又因为第一个数不是1,所以为了让第一个数字典序最小,去掉1后的序列字典序要最小,与1有关的逆序对个数要最多。
那么1放在最后就好啦!
放好1后,2~n其实也可以看作1~n-1来处理,所以一步步来即可。
代码:
#include
#include
#include
#include
using namespace std;
#define LL long long
int n,m,l,r;
int ans[50005];
int main()
{
scanf("%d%d",&n,&m);
l=1,r=n;
for(int i=1;i<=n;++i) {
int kl=(LL)(n-i)*(n-i-1)/2;
if(kl>=m) ans[l++]=i;
else m-=n-i,ans[r--]=i;
}
for(int i=1;i<=n;++i) printf("%d ",ans[i]);
return 0;
}
难度评级: ※※※※※(对于以前没做过类似问题的人来说)
题目分析:
由于思维太巧了所以给的评级较高。
卡特兰数的变形。
如图(从洛谷题解区搬过来的图(⊙﹏⊙)b)
把选择1看作向右上走,选择0看作往左下走,那么就是要从(0,0)走到(n+m,n-m)位置,走法总数为 Cmn+m 种。
而不合法的走法就是触碰到了直线y=-1。由对称性,所有从(0,0)出发碰到过y=-1走到(n+m,n-m)的走法均可以看作从(0,-2)出发走到(n+m,n-m),走法总数为 Cm−1n+m 种。
答案就是 Cmn+m−Cm−1n+m
代码:
#include
#include
#include
#include
using namespace std;
#define LL long long
LL mod=20100403,n,m,k1,k2,k3,k4,ans;
LL ksm(LL x,LL y) {
LL re=1;
while(y) {
if(y&1) re=re*x%mod;
x=x*x%mod,y>>=1;
}
return re;
}
int main()
{
scanf("%lld%lld",&n,&m);
k1=k2=k3=k4=1;
for(int i=n+1;i<=n+m;++i) k1=k1*i%mod;
for(int i=1;i<=m;++i) k2=k2*i%mod;
ans=k1*ksm(k2,mod-2)%mod;
for(int i=n+2;i<=n+m;++i) k3=k3*i%mod;
for(int i=1;i<=m-1;++i) k4=k4*i%mod;
ans=(ans-k3*ksm(k4,mod-2)%mod+mod)%mod;
printf("%lld",ans);
return 0;
}
难度评级: ※※※※
题目分析:
这题难度不在数学。
我们令一块公墓上下左右的常青树数量分别为u,d,l,r,那么这块公墓的虔诚度就是 Cku∗Ckd∗Ckl∗Ckr
会发现由于常青树很少,所以有很多公墓都是没有虔诚度的。所以比起公墓,还是枚举常青树比较科学。
我们首先离散一下常青树的横纵坐标。然后按照横坐标从小到大枚举每一列(指横坐标相同的),对于某一列相邻的两棵常青树,处理夹在他们中间的公墓贡献(这个等下说)。然后处理完这一列后,每一棵树纵坐标的贡献相应的发生变化,用树状数组维护。
我肯定没讲明白,看代码吧。
代码:
#include
#include
#include
#include
using namespace std;
#define uin unsigned int
uin read() {
uin q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005;
uin n,m,w,tx,ty,k,ans;
struct tree{uin x,y;}a[N];
uin by[N],bx[N],c[N][11],xjs[N],yjs[N],tr[N],tmp[N],nowy[N];
//by,bx:用于离散x和y坐标
//c:组合数
//xjs,yjs:每一个x和y坐标有多少常青树
//tr:树状数组
//tmp:记录每一个y坐标的贡献
//nowy:当前已经处理了的这个y坐标上的常青树有多少棵(即在左边)
void init() {//打表组合数
for(int i=0;i<=w;++i) {
c[i][0]=1;
for(int j=1;j<=i&&j<=k;++j) c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
uin lowbit(uin x) {return x&(-x);}
uin find(uin x) {
uin re=0;
while(x) re+=tr[x],x-=lowbit(x);
return re;
}
void add(uin x,uin num) {while(x<=ty) tr[x]+=num,x+=lowbit(x);}
void work() {
uin cnt=0;
for(int i=1;iif(a[i+1].x==a[i].x) {
int ygx=find(a[i+1].y-1)-find(a[i].y);//y坐标总贡献
ans+=ygx*c[cnt][k]*c[xjs[a[i].x]-cnt][k];
}
else cnt=0;
//修改y坐标贡献
add(a[i].y,-tmp[a[i].y]);
tmp[a[i].y]=c[nowy[a[i].y]][k]*c[yjs[a[i].y]-nowy[a[i].y]][k];
add(a[i].y,tmp[a[i].y]);
}
}
bool cmp(tree xx,tree yy) {
if(xx.x==yy.x) return xx.yreturn xx.xint main()
{
n=read(),m=read(),w=read();
for(int i=1;i<=w;++i) {
a[i].x=read(),a[i].y=read();
bx[i]=a[i].x,by[i]=a[i].y;
}
sort(a+1,a+1+w,cmp),sort(bx+1,bx+1+w),sort(by+1,by+1+w);
tx=1;for(int i=1;i<=w;++i) if(bx[i]!=bx[tx]) bx[++tx]=bx[i];
ty=1;for(int i=1;i<=w;++i) if(by[i]!=by[ty]) by[++ty]=by[i];
//离散
for(int i=1;i<=w;++i) {
a[i].x=lower_bound(bx+1,bx+1+tx,a[i].x)-bx;
a[i].y=lower_bound(by+1,by+1+ty,a[i].y)-by;
++xjs[a[i].x],++yjs[a[i].y];
}
k=read(),init(),work();
printf("%d\n",ans&2147483647);
return 0;
}
难度评级: ※※※
题目分析:
#include
#include
#include
#include
using namespace std;
#define LL long long
LL n,k,ans;
int main()
{
scanf("%lld%lld",&k,&n);
LL i,j;ans=n*k;
if(k>n) k=n;
for(i=1;i<=k;i=j+1) {
j=n/(n/i);if(j>k) j=k;
ans-=(j-i+1)*(i+j)/2*(n/i);
}
printf("%lld",ans);
return 0;
}
难度评级:※
题目分析:
用f(x,zt,las)表示处理到第x个块,第x个块是否有雷(zt),上一个块是否有雷(las),这个状态已经获得的前提下,还可以产生多少种方案。
然后记忆化搜索即可。
代码:
#include
#include
#include
#include
using namespace std;
#define LL long long
const int N=10005;
LL f[N][2][2];int n,a[N];
LL dfs(int x,int zt,int las) {
if(x==n+1) {
if(zt) return 0;
else return 1;
}
LL &re=f[x][zt][las];
if(re!=-1) return re;
if(!a[x]) {
if(zt||las) return 0;
re=dfs(x+1,0,0);
}
else if(a[x]==1) {
if(zt&&las) return 0;
if(las) re=dfs(x+1,0,0);
else if(zt) re=dfs(x+1,0,1);
else re=dfs(x+1,1,0);
}
else if(a[x]==2) {
if(!zt&&!las) return 0;
if(zt&&las) re=dfs(x+1,0,1);
else if(zt) re=dfs(x+1,1,1);
else if(las) re=dfs(x+1,1,0);
}
else {
if(!zt||!las) return 0;
re=dfs(x+1,1,1);
}
return re;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
memset(f,-1,sizeof(f));
printf("%lld",dfs(1,0,0)+dfs(1,1,0));
return 0;
}