给你一个N(N<=3e3),从1-N中随机选出一个数n,不妨令ans[i]为长度为i的排列的逆序对期望
先计入ans[n],再将该排列变成n的一个子序列(不妨长度为p,0<=p<=n),
再统计其期望值ans[p],再重复该过程,直到子序列长度为0为止
求对于给定的n,统计进去的期望值的和是多少
思路来源:https://www.cnblogs.com/nlKOG/p/11243080.html
概率dp,首先如果不考虑向子序列转移,
n的排列中共有个逆序对,在一半的排列中他们表现为逆序对,另一半表现为正序对,所以出现概率
故计ans[i]为长度为i的排列的逆序对期望,ans[i]应为,
剩下的,概率dp的套路了,dp[n]=dp[n]+所有dp[子情况]
以上图片来自思路来源,由于右边有f(n)所以需移项,具体处理时注意对f(n)分子分母同乘,处理好逆元
最后res[i]先求一次前缀和,再对每项乘一下N的逆元即可
#include
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=3e3;
ll Finv[maxn+5],jc[maxn+5];
ll n,ans[maxn+5];
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;x=x*x%mod,n/=2)
if(n&1)res=res*x%mod;
return res;
}
ll inv(ll x)
{
return modpow(x,mod-2,mod);
}
void init()
{
jc[0]=Finv[0]=1;
for(int i=1;i<=maxn;++i)
{
jc[i]=jc[i-1]*i;
if(jc[i]>=mod)jc[i]%=mod;
}
Finv[maxn]=modpow(jc[maxn],mod-2,mod);
for(int i=maxn-1;i>=1;--i)
{
Finv[i]=Finv[i+1]*(i+1);
if(Finv[i]>=mod)Finv[i]%=mod;
}
}
ll C(ll n,ll m)
{
if(m<0||m>n)return 0;
return jc[n]*Finv[n-m]%mod*Finv[m]%mod;
}
void init2()
{
ll v=inv(4);
for(ll i=2;i<=maxn;++i)
{
ans[i]=i*(i-1)*v%mod;
ans[i]=ans[i]*modpow(2,i,mod)%mod;
for(ll j=0;j
有一个由n(n≤500)个人组成的军队,每个人都可以担任战士或法师。
军队中存在m(m≤1e4)个关系,第i个关系中包括两个人li,ri。
当两人均为战士时,军队总战斗力上升Ai,
当一人为战士一人为法师时,军队总战斗力上升Bi,
当两人均为法师时,军队总战斗力上升Ci,
保证Bi=Ai/4+Ci/3且Bi为整数,求军队能获得的最大总战斗力。
思路来源:https://blog.csdn.net/weixin_42789801/article/details/97287137
最大流构建不出模型来,拆成两个点却只能流其中一个点,所以考虑最小割模型
先假设获得了所有收益Ai+Bi+Ci,再使损失最小
x和y同属s集合时,代表两人同为战士,获得Ai,也就是割掉边c和边d,损失Bi+Ci;
x和y同属t集合时,代表两人同为法师,获得Ci,也就是割掉边a和边b,损失Ai+Bi;
其他情况,比如割ad,或者割bc,获得Bi,损失的是Ai+Ci
最后ans等于
#include
#include
#include
#include
#include
要统计所有[l,r]为回文串且[l,(l+r)/2向下取整]为回文串,
每出现这样一个[l,r],其贡献算在i=(r-l+1)的长度i里,对i的贡献+1
对于每个长度i,输出其贡献
思路来源:福州大学标程代码
用回文树先处理出所有本质不同的子串,每一个子串对应一个节点
在节点i里记录该回文串长度为len[i],其最后一个字母在PAM中s的位置为no[i]
则该回文串其实际上对应在串中的位置为[no[i]-len[i]+1,no[i]],
manacher预处理原序列,注意PAM中下标为1-n,而manacher中是0-(n-1)
处理好之后,直接判前半段和后半段是否至少有一段为回文串即可(其实似乎判一段就行了)
看懂了代码,但是不会自己敲,只好加点注释之后,粘标程
人家的manacher非常短,调下标还不麻烦,以后就用这个了,省去调下标的过程
人家的回文树的板子也是功能很全,还支持fail树上倍增的操作
#include
using namespace std;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define rep(i, a, b) for(int i=(a); i<(b); i++)
#define per(i, a, b) for(int i=(b)-1; i>=(a); i--)
#define sz(a) (int)a.size()
#define de(a) cout << #a << " = " << a << endl
#define dd(a) cout << #a << " = " << a << " "
#define all(a) a.begin(), a.end()
#define pw(x) (1ll<<(x))
#define endl "\n"
typedef long long ll;
typedef pair pii;
typedef vector vi;
typedef double db;
#define mem(a,x) memset(a,x,sizeof(a))
const int N=3e5+5;
char s[N];
int pa[N<<1],n,ret[N];
void Manacher(char *s,int n,int *pa){
//s[0]对应pa[0]
//s[1]对应pa[2]
//s[i]对应pa[2*i]
//奇数位置为原#号位置 此处pa半径是不考虑#的 但考虑中心 如aba对于b的回文半径为2
pa[0] = 1;
for(int i=1,j=0;i<(n<<1)-1;++i){
int p = i >> 1 , q = i - p , r = ((j + 1)>>1) + pa[j] - 1;
pa[i] = r < q ? 0 : min(r - q + 1 , pa[(j<<1) - i]);
while(0 <= p - pa[i] && q + pa[i] < n && s[p - pa[i]] == s[q + pa[i]])
pa[i]++;
if(q + pa[i] - 1 > r) j = i;
}
}
inline bool check(int L,int R) {//询问[L,R]是否为回文串 0<=L<=R>1;
//实质是 回文半径 应为 串长/2向上取整 aba对应2 bb对应1
if ((L&1)==(R&1)) return pa[mid<<1]>=R-L+2>>1;//长度为奇的串 a#b#a 0-2 (R-L+2)/2==2 即ab的长度
return pa[mid<<1|1]>=R-L+1>>1;//长度为偶的串 b#b 0-1 (R-L+1)/2==1 即b的长度 (不考虑#)
}
inline void Put(string &s,vi &V) {
V.clear();
rep(i,0,sz(s)) V.pb(s[i]-'a');
}
// 关于((L&1)!=(R&1))的说明
// LR同奇偶 串长为奇 后半段应从mid开始 故不加
// LR异奇偶 串长为偶 后半段应从mid+1开始 故+1
inline bool Check(int L,int R) {
int mid=L+R>>1;
if (!check(L,mid) || !check(mid+((L&1)!=(R&1)),R)) return false;
return true;
}
const int M=26;
struct PAM{
int s[N],len[N],next[N][M],fail[N],cnt[N],dep[N],id[N],no[N],last,n,p,cur,now,f[N][20];
ll ans;
inline int new_node(int _l) { mem(next[p],0); cnt[p]=dep[p]=0,len[p]=_l; return p++; }
inline void Init() { new_node(p=0),new_node(s[0]=-1),fail[last=n=0]=1; }
inline int get_fail(int x) { for (; s[n-len[x]-1]!=s[n]; x=fail[x]); return x; }
inline void I(int c) {
c-='a',s[++n]=c,cur=get_fail(last);
if (!next[cur][c]) {
now=new_node(len[cur]+2);
fail[now]=next[get_fail(fail[cur])][c];
next[cur][c]=now;
dep[now]=dep[fail[now]]+1;
}
last=next[cur][c]; cnt[last]++;
id[n]=last,no[last]=n; //id[第n个字符]=第i个节点 no[第last个节点]=第n个字符 记录下在原串中的位置下标 从1开始
}
inline void Insert(char s[],int op=0,int _n=0) {//正倒序插入
if (!_n) _n=strlen(s); if (!op) rep(i,0,_n) I(s[i]); else per(i,0,_n) I(s[i]);
}
inline void count() { per(i,0,p) cnt[fail[i]]+=cnt[i]; }
inline int Find(int x,int L) {//去找节点x的回文串长不超过l的最近祖先(可以为自己)编号 倍增lca
if (len[x]<=L) return x;
per(i,0,20) if (len[f[x][i]]>L) x=f[x][i];
return fail[x];
}
inline void init() {//fail树预处理倍增
mem(f,0);
rep(i,0,p) f[i][0]=fail[i];
rep(i,0,p) rep(j,1,20) f[i][j]=f[f[i][j-1]][j-1];
}
inline void Q() {
count();
rep(i,2,p) no[i]--;//为了对应0-(n-1)的下标
rep(i,0,p) if (len[i]>0)
ret[len[i]]+=Check(no[i]-len[i]+1,no[i])*cnt[i];
//每个本质不同的回文串 用整段去判前半段是否回文
}
} TT;
int main() {
while (scanf("%s",s)!=EOF) {
n=strlen(s); Manacher(s,n,pa);
mem(ret,0);
TT.Init(),TT.Insert(s),TT.Q();
rep(i,1,n+1) printf("%d%c",ret[i]," \n"[i==n]);
}
return 0;
}
水题,输出n(n<=1e9)的阶乘mod p(p==1e6+3)
N(N<=1e5)个火柴棒,第i根长度为ai(1<=ai<=1e9)
Q(Q<=1e5)个询问,第j次询问只用[lj,rj]内的火柴棒,
询问若其中取出三根构成三角形,三角形的最大周长是多少
划分树不会,用的主席树
如果不能构成火柴棒,增长最密集的数列是斐波那契数列,即第三个数等于前两个数之和
但即便是fib数列,增长到1e9也只需要44项,这说明1e9内密集的数,44个数中一定会出现三根火柴棒
于是变成询问区间第一大到区间44大问题,主席树板子题,
板子是个从小到大的第kth,那就倒着询问,从第len开始呀,
注意少mod,本来想保留123向234转移的时候只把1丢掉,沿用23的结果,
后来发现这不如直接重问快,因为写一个循环的三个节点每次都得mod
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+10;
//主席树 与上一棵树共用没有新开的那些节点
//root数组代表第i颗树的根
//插入和查询都是 两棵树同步走相对应的节点
typedef long long ll;
int cnt,root[maxn],n,m,l,r;
int a[maxn],Rank[maxn];
ll res,ans[3];
struct node
{
int lson,rson,num;
//分别是左子下标 右子下标 这段值域出现的个数
}tree[maxn*40];
//pre代表上一颗子树的根 cur代表当前子树的根 v代表要插的值
void add(int pre,int &cur,int l,int r,int v)
{
//cur的引用保证了cur赋新值之后,能直接更新递归上层的tree[cur].son
cur=++cnt;
tree[cur].num=tree[pre].num+1;
if(l==r)return;
tree[cur].lson=tree[pre].lson,tree[cur].rson=tree[pre].rson;
int mid=(l+r)>>1;
if(v<=mid)add(tree[pre].lson,tree[cur].lson,l,mid,v);
else add(tree[pre].rson,tree[cur].rson,mid+1,r,v);
}
int query(int L,int R,int l,int r,int kth)
{
if(l==r)return l;//二分区间直至单点满足 不满足返回0
int mid=(l+r)>>1;
int diff=tree[tree[R].lson].num-tree[tree[L].lson].num;
if(diff>=kth)return query(tree[L].lson,tree[R].lson,l,mid,kth);
else return query(tree[L].rson,tree[R].rson,mid+1,r,kth-diff);
}
int main()
{
//确保所有值都在1到n之间 如果不在的话 离散化一下就搞成1到n之间了
//插第一条链的时候 不用管pre节点里值是什么 认为全都是0也无妨
//也可以先build一棵普通权值线段树 然后再add节点
root[0]=0,tree[0].lson=tree[0].rson=tree[0].num=0;
while(~scanf("%d%d",&n,&m))
{
cnt=0;
for(int i=1;i<=n;++i)
{
int v;
scanf("%d",&a[i]);
Rank[i]=a[i];
}
sort(Rank+1,Rank+n+1);//离散化一波
int mx=unique(Rank+1,Rank+n+1)-(Rank+1);
//printf("%d\n",mx);
for(int i=1;i<=n;++i)
{
a[i]=lower_bound(Rank+1,Rank+mx+1,a[i])-Rank;//保证最小也是1 现在ai只是一个排名
add(root[i-1],root[i],1,mx,a[i]);
}
//前缀和思想 [l,r]信息只需将第root[r]棵树与第root[l-1]棵树作差
while(m--)
{
scanf("%d%d",&l,&r);
int len=r-l+1;
if(len<3){puts("-1");continue;}
res=-1;
for(int rk=len;rk>=3;--rk)
{
for(int j=0;j<3;++j)
ans[j]=Rank[query(root[l-1],root[r],1,mx,rk-j)];
if(ans[0]
n(n<=1e5)个数,每个数在1到C之间(C<=1e5),设最少出现次数为K(K<=1e5)
求一个最长的子区间[l,r],使得[l,r]内的元素,均出现不少于K次
特判k==1的情形,为长度n
其余情况,考虑线段树+双端队列,
last[v]记v上一次出现的位置,加上一个值v的时候,先将v单点赋值成C,
再将[last[v]+1,v]区间-1,代表这一段因为一个值次数小于K从而不可取,并加入双端队列
当双端队列长度为k时,将k处的区间的值+1,代表如果固定右端点为现在的v的时候,
左端端点是可以取到恢复了的位置,即单点值等于C的位置的,
那么端点如果有一个值小于C,说明至少受一个值的约束从而不可取,
所以对于枚举的每个右端点,答案就是满足单点值加回C的最左端点,更新答案即可
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+10;
int dat[maxn*5],cov[maxn*5];
int n,c,k,ans,a[maxn];
dequee[maxn];
void build(int p,int l,int r)
{
dat[p]=cov[p]=0;
if(l==r)return;
int mid=(l+r)/2;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void pushup(int p)
{
dat[p]=max(dat[p<<1],dat[p<<1|1]);
//其实是维护这两段和是否大于0
//正+正>0,负+负<0 如果两段和>0一定是大正+小负
}
void pushdown(int p,int l,int r)
{
if(cov[p])
{
int mid=(l+r)/2;
dat[p<<1]+=cov[p];
dat[p<<1|1]+=cov[p];
cov[p<<1]+=cov[p];
cov[p<<1|1]+=cov[p];
cov[p]=0;
}
}
void update(int p,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr)
{
dat[p]+=v;
cov[p]+=v;
return;
}
pushdown(p,l,r);
int mid=(l+r)/2;
if(ql<=mid)update(p<<1,l,mid,ql,qr,v);
if(qr>mid)update(p<<1|1,mid+1,r,ql,qr,v);
pushup(p);
}
int ask(int p,int l,int r)
{
if(l==r)return dat[p]==c?l:-1;
pushdown(p,l,r);
int mid=(l+r)/2;
if(dat[p<<1]==c)return ask(p<<1,l,mid);
if(dat[p<<1|1]==c)return ask(p<<1|1,mid+1,r);//不会询问到[i+1的区域 因为没更新
return -1;
}
int main()
{
while(~scanf("%d%d%d",&n,&c,&k))
{
ans=0;
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
if(k==1){printf("%d\n",n);continue;}
for(int i=1;i<=c;++i)
{
e[i].clear();
e[i].push_back(0);
}
build(1,1,n);
for(int i=1;i<=n;++i)//枚举右端点i 满足条件的应为左端被拔回原高的
{
int v=a[i];
update(1,1,n,i,i,c);//初始高度为c
if(e[v].size()==k)
{
int l=e[v].front();
e[v].pop_front();
int r=e[v].front();
update(1,1,n,l+1,r,1);//满足连续k个的条件 将这一段+1升回来
}
int x=e[v].back();
update(1,1,n,x+1,i,-1);//新加的这一段不符合条件 降下去-1
e[v].push_back(i);
int pos=ask(1,1,n);//查询最左 高度为c的值
if(pos!=-1)ans=max(ans,i-pos+1);
}
printf("%d\n",ans);
}
return 0;
}