D - Anything Goes to Zero
设串
X
X
X中
1
1
1的个数为
s
u
m
sum
sum,若翻转的是
1
1
1,
1
1
1的个数减
1
1
1,若翻转的是
0
0
0,
1
1
1的个数加
1
1
1,故我们得到两个模数
s
u
m
−
1
sum-1
sum−1和
s
u
m
+
1
sum+1
sum+1,故两个模数分开处理翻转
0
0
0和
1
1
1的情况。
计算
f
(
X
i
)
f(X_i)
f(Xi),第一次取模
s
u
m
−
1
sum-1
sum−1或
s
u
m
+
1
sum+1
sum+1会使得
x
i
<
=
2
e
5
x_i<=2e5
xi<=2e5,可以对前
2
e
5
2e5
2e5打表,也可以直接计算,因为计算
f
(
X
i
)
f(X_i)
f(Xi)的时间复杂度显然为
O
(
l
o
g
X
i
)
O(logX_i)
O(logXi)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=2e5+5;
ll p[N];
int pre[N],bk[N],ans[N];
int n;
char s[N];
int get(int x)
{
int ans=0;
while(x)
x=x%__builtin_popcount(x),ans++;
return ans;
}
void solve(int x,int sum)
{
if(sum<=0) return;
p[0]=1;for(int i=1;i<N;i++) p[i]=p[i-1]*2%sum;
for(int i=1;i<=n;i++)
pre[i]=(pre[i-1]+(s[i]=='1')*p[i-1])%sum;
for(int i=n;i>=1;i--)
bk[i]=(bk[i+1]+(s[i]=='1')*p[i-1])%sum;
for(int i=1;i<=n;i++)
if(x==1&&s[i]=='1')
{
int res=(pre[i-1]+bk[i+1])%sum;
ans[i]=1+get(res);
}
else if(x==0&&s[i]=='0')
{
int res=(pre[i-1]+bk[i+1]+p[i-1])%sum;
ans[i]=1+get(res);
}
}
int main()
{
scanf("%d%s",&n,s+1);
reverse(s+1,s+1+n);
int sum=0;
for(int i=1;i<=n;i++) sum+=s[i]=='1';
solve(1,sum-1);
solve(0,sum+1);
reverse(ans+1,ans+1+n);
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
}
E - Camel Train
若
l
=
r
l=r
l=r,无需考虑它的位置,直接加入总答案。
若
l
>
r
l>r
l>r,把它分为第一类。
若
l
>
r
l>r
l>r,令其
k
=
n
−
k
k=n-k
k=n−k,交换他们的
l
,
r
l,r
l,r,并分其为第二类。
设计
s
0
,
i
s_{0,i}
s0,i为第一类放前
i
i
i个位置的最大收益(即第一类中,对于某个下标
j
j
j,如果
j
j
j放在前
i
i
i个位置则其收益为
l
j
l_j
lj,否则为
r
j
r_j
rj)。
设计
s
1
,
i
s_{1,i}
s1,i为第二类放后
i
i
i个位置的最大收益。
那么第一类和第二类的最大收益为
m
a
x
(
s
u
m
0
,
i
+
s
u
m
1
,
n
−
i
)
(
i
=
[
0
,
1
,
2
,
.
.
.
,
n
−
1
,
n
]
)
max(sum_{0,i}+sum_{1,n-i})(i=[0,1,2,...,n-1,n])
max(sum0,i+sum1,n−i)(i=[0,1,2,...,n−1,n])。
考虑如何计算
s
0
,
i
s_{0,i}
s0,i,对于
s
1
,
i
s_{1,i}
s1,i为相同的情况。
考虑其顺序,比较两只不同的骆驼
i
,
j
i,j
i,j,要使得
l
i
+
r
j
>
l
j
+
r
i
l_i+r_j>l_j+r_i
li+rj>lj+ri,即
l
i
−
r
i
>
l
j
−
r
j
l_i-r_i>l_j-r_j
li−ri>lj−rj,对于
l
i
−
r
i
=
l
j
−
r
j
l_i-r_i=l_j-r_j
li−ri=lj−rj的,我们让
k
k
k较大的排在前面。
然后,枚举位置的个数
i
i
i,每次增加一个位置贪心的加入收益最大的。
如何判断一只骆驼
k
j
,
l
j
,
r
j
k_j,l_j,r_j
kj,lj,rj可以加入前
i
i
i个位置?
设计
f
1
,
f
2
,
.
.
.
,
f
n
f_1,f_2,...,f_n
f1,f2,...,fn,
f
u
f_u
fu分别表示前
u
u
u个位置剩余的可用位置的数量。
每次加入一个骆驼时
j
j
j时,我们贪心它加入第
k
j
k_j
kj个位置(因为只要
j
j
j前面有空位,我们可以自由地把它移动到空位里),那么更新
f
k
j
,
k
j
+
1
,
.
.
.
,
n
=
f
k
j
,
k
j
+
1
,
.
.
.
,
n
−
1
f_{k_j,k_j+1,...,n}=f_{k_j,k_j+1,...,n}-1
fkj,kj+1,...,n=fkj,kj+1,...,n−1。
如果某个时刻对于某个
u
u
u有
f
u
=
0
f_u=0
fu=0,即
u
u
u前面的位置已经被占满了,那么对于
k
j
<
=
u
k_j<=u
kj<=u的
j
j
j骆驼不可以加入前
u
u
u个位置。
于是可以用线段树维护区间的最小值的最大端点,至此问题可以在
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)时间内解决。
比赛时候太困了,以至于线段树都能写错,在此留图做个纪念。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=2e5+5;
struct node
{
int k,l,r;
node(int k=0,int l=0,int r=0):k(k),l(l),r(r){}
};
struct haha
{
int id,v,k;
haha(int id=0,int v=0,int k=0):id(id),v(v),k(k){}
bool operator<(const haha&o)const
{
if(v==o.v) return k<o.k;
return v<o.v;
}
};
vector<node>vl,vr;
int n,t[N<<2],lz[N<<2],ss[N];
ll ans=0;
ll s[2][N];
void update(int l,int r,int k)
{
assert(t[k]>=0);
if(l!=r)
{
t[k<<1]-=lz[k];t[k<<1|1]-=lz[k];
lz[k<<1]+=lz[k];lz[k<<1|1]+=lz[k];
t[k]=min(t[k<<1],t[k<<1|1]);
}
lz[k]=0;
}
int query(int l,int r,int k)
{
if(lz[k]) update(l,r,k);
if(t[k]>0) return 0;
if(l==r) return l;
int m=l+r>>1;
if(t[k<<1|1]<=0) return query(m+1,r,k<<1|1);
return query(l,m,k<<1);
}
void fix(int l,int r,int k,int x,int y)
{
if(lz[k]) update(l,r,k);
if(r<x||l>y) return;
if(l>=x&&r<=y)
{
lz[k]++;t[k]--;update(l,r,k);return;
}
int m=l+r>>1;
fix(l,m,k<<1,x,y);fix(m+1,r,k<<1|1,x,y);
t[k]=min(t[k<<1],t[k<<1|1]);
}
void build(int l,int r,int k)
{
t[k]=l;lz[k]=0;
if(l==r) return;
int m=l+r>>1;
build(l,m,k<<1);build(m+1,r,k<<1|1);
t[k]=l;
}
void solve(vector<node>v,int p)
{
build(1,n,1);
priority_queue<haha>q;
ll sum=0;
for(int i=0;i<v.size();i++) sum+=v[i].r,q.push(haha(i,v[i].l-v[i].r,v[i].k));
s[p][0]=sum;
for(int i=1;i<=n;i++)
{
int pos=query(1,n,1);
//cout<<pos<<' '<<t[1]<<endl;
while(!q.empty())
{
int id=q.top().id;q.pop();
if(v[id].k==0) continue;
if(v[id].k>pos)
{
//cout<<v[id].l<<' '<<v[id].r<<' '<<v[id].k<<endl;
sum=sum-v[id].r+v[id].l;
fix(1,n,1,v[id].k,n);
break;
}
}
s[p][i]=max(sum,s[p][i-1]);
}
}
fstream in,out;
void getts()
{
int t=50;
in.open("1.txt");
in<<t<<endl;
while(t--)
{
int n=5;
in<<n<<endl;
for(int i=1;i<=n;i++)
{
int k=rand()%n+1,l=rand()%100+1,r=rand()%100+1;
in<<k<<' '<<l<<' '<<r<<endl;
}
}
in.close();
}
bool de=false;
int main()
{
srand(time(0));
//getts();freopen("1.txt","r",stdin);out.open("out.txt");de=true;
int t;scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
vl.clear();vr.clear();ans=0;
for(int i=1;i<=n;i++)
{
int k,l,r;
scanf("%d%d%d",&k,&l,&r);
if(l==r) ans+=l;
else if(l>r) vl.push_back({k,l,r});
else vr.push_back({n-k,r,l});
}
for(int i=0;i<=n;i++) s[0][i]=s[1][i]=0;
solve(vl,0);
solve(vr,1);
ll res=0;
for(int i=0;i<=n;i++)
res=max(res,s[0][i]+s[1][n-i]);
if(!de) printf("%lld\n",ans+res);
if(de) out<<ans+res<<endl;
}
}
F Two Snuke
定义
d
s
=
s
2
−
s
1
,
d
n
=
n
2
−
n
1
,
d
u
=
u
2
−
u
1
,
d
k
=
k
2
−
k
1
,
d
e
=
e
2
−
e
1
ds=s_2-s_1,dn=n_2-n_1,du=u_2-u_1,dk=k_2-k_1,de=e_2-e_1
ds=s2−s1,dn=n2−n1,du=u2−u1,dk=k2−k1,de=e2−e1。
再定义
s
=
2
∗
s
1
,
n
=
2
∗
n
1
,
u
=
2
∗
u
1
,
k
=
2
∗
k
1
,
e
=
2
∗
e
1
s=2*s_1,n=2*n_1,u=2*u_1,k=2*k_1,e=2*e_1
s=2∗s1,n=2∗n1,u=2∗u1,k=2∗k1,e=2∗e1。
问题等价于找到对于满足以下条件的所有划分,求所有
d
s
∗
d
n
∗
d
u
∗
d
k
∗
d
e
ds*dn*du*dk*de
ds∗dn∗du∗dk∗de之和:
- 0 < = s , n , u , k , e 0<=s,n,u,k,e 0<=s,n,u,k,e
- s , n , u , k , e 是 偶 数 s,n,u,k,e是偶数 s,n,u,k,e是偶数
- 1 < = d s , d n , d u , d k , d e 1<=ds,dn,du,dk,de 1<=ds,dn,du,dk,de
- s + n + u + k + e + d s + d n + d u + d k + d e < = N s+n+u+k+e+ds+dn+du+dk+de<=N s+n+u+k+e+ds+dn+du+dk+de<=N
划分的方案数等价于 N N N个球分为十一堆,扔掉最后一堆球,剩余的前 5 5 5堆球为 s , n , u , k , e s,n,u,k,e s,n,u,k,e,后 5 5 5堆球为 d s , d n , d u , d k , d e ds,dn,du,dk,de ds,dn,du,dk,de。
再考虑每种方案中的权值 d s ∗ d n ∗ d u ∗ d k ∗ d e ds*dn*du*dk*de ds∗dn∗du∗dk∗de,等价于再在后五堆球每堆各选一个球的方案数。
在每堆球中选择一个后,把该球删除,并把在这堆中编号小于被删除的球的编号和编号大于被删除的球的编号再分为两堆,这样,问题等价于:
求
N
−
5
N-5
N−5个球排成一排,将其分为
16
16
16堆,使得前
5
5
5堆球的个数为偶数的方案数。
在此问题基础上设计 D p Dp Dp,再采用矩阵加速即可。
#include<bits/stdc++.h>
#define mems(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
const int MS=30,mod=1e9+7;
struct Mat//矩阵
{
ll a[MS][MS];
int n,m;
Mat(int n=0,int m=0):n(n),m(m) { mems(a,0);}
Mat operator*(const Mat&B)const
{
Mat C(n,B.m);
for(int i=1;i<=n;i++)
for(int j=1;j<=B.m;j++)
for(int k=1;k<=m;k++)
C.a[i][j]=(C.a[i][j]+a[i][k]*B.a[k][j])%mod;
return C;
}
};
Mat qpow(Mat a,int n)
{
Mat ans(a.n,a.n);
for(int i=1;i<=a.n;i++) ans.a[i][i]=1;
while(n)
{
if(n&1) ans=ans*a;
a=a*a;
n>>=1;
}
return ans;
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
int n;scanf("%d",&n);
n+=10;
Mat A(21,21);
for(int i=1;i<=5;i++) A.a[16+i][i]=1;
for(int i=2;i<=5;i++) A.a[i-1][i]=1;
for(int i=6;i<=16;i++) A.a[i-1][i]=A.a[i][i]=1;
for(int i=17;i<=21;i++) A.a[i-16][i]=1;
A=qpow(A,n);
printf("%lld\n",A.a[1][16]);
}
}