USACO 2023 Feb 游记
由于上次打完铜组后错误地选择了继续开银组并不堪倦意导致银组摆烂爆零,这次从银组继续打并立志一路打到铂金,然后实现了目标。这次吸取了上次的教训,每天只开一套题,其中银组是 2.25 回寝室打到转钟,金组是 2.26 在(家还是学校不记得了)打的,铂金是 2.27 在机房打的。银组和金组均赛时 AK,铂金组赛时没有过题。
Silver
Bakery
这题调了蛮久,原因是记错题意了,写的是 \(x\in [1,t_M]\),实际上应该是 \(x\in [0,t_M-1]\);还有就是精度问题,以为可以用 long double
过,结果不行,写了一个很丑的 long long
实现下取整。至于题解,就略了。
Bakery (C++11)
Yule Peng (pengyule)
Submitted: Fri, Feb 24, 2023 09:07:10 EST
Contest: USACO 2023 February Contest, Silver
#include <bits/stdc++.h>
#define pii pair<ll,ll>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
register char ch=getchar();register ll x=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
void print(int x){
if(x<0){putchar('-'),print(-x);return;}
if(x/10)print(x/10);
putchar(x%10+48);
}
const int N=105,INF=1e9+7;
int n,tc,Tm;
ll a[N],b[N],c[N];
pii jiao(pii a,pii b){
return make_pair(max(a.first,b.first),min(a.second,b.second));
}
bool check(ll s){
pii Ran=make_pair(-INF,INF);
for(int i=1;i<=n;i++){
pii ran=make_pair(-INF,INF);
if(a[i]>b[i]){
ll o=(a[i]*tc+b[i]*Tm-c[i]-b[i]*s)/(a[i]-b[i]);
if(o*(a[i]-b[i])<(a[i]*tc+b[i]*Tm-c[i]-b[i]*s))o++;
if((o-1)*(a[i]-b[i])>=(a[i]*tc+b[i]*Tm-c[i]-b[i]*s))o--;
ran=make_pair(o,INF);
}
else if(a[i]<b[i]){
ll o=(a[i]*tc+b[i]*Tm-c[i]-b[i]*s)/(a[i]-b[i]);
if(((a[i]*tc+b[i]*Tm-c[i]-b[i]*s)<0)!=((a[i]-b[i])<0))return 0;
ran=make_pair(0,o);
}
else if(a[i]*tc+b[i]*Tm-c[i]-b[i]*s>0)return 0;
Ran=jiao(Ran,ran);
}
Ran=jiao(Ran,make_pair((max(s-min(s,Tm-1ll),0ll)),min(s,(ll)tc-1)));
if(Ran.first>Ran.second)return 0;
return 1;
}
void solve(){
n=read(),tc=read(),Tm=read();
for(int i=1;i<=n;i++){
a[i]=read(),b[i]=read(),c[i]=read();
}
ll L=1,R=tc+Tm+1,mid;
while(L<R-1){
mid=L+R>>1;
if(check(mid))R=mid;
else L=mid;
}
cout<<R<<'\n';
}
int main(){
int T=read();
while(T--)solve();
return 0;
}
Cow-libi
好像也调了 20 分钟,因为一些小错误。
Cow-libi (C++11)
Yule Peng (pengyule)
Submitted: Fri, Feb 24, 2023 09:37:29 EST
Contest: USACO 2023 February Contest, Silver
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline int read(){
register char ch=getchar();register int x=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
void print(int x){
if(x<0){putchar('-'),print(-x);return;}
if(x/10)print(x/10);
putchar(x%10+48);
}
map<int,pair<int,int> >mp;
ll dist(pair<int,int>a,pair<int,int>b){
return 1ll*(a.first-b.first)*(a.first-b.first)+1ll*(a.second-b.second)*(a.second-b.second);
}
int m,n;
struct node {
int x,y,t;
}a[100005];
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++){
int x=read(),y=read(),t=read();
mp[t].first=x,mp[t].second=y;
a[i].x=x,a[i].y=y,a[i].t=t;
}
sort(a+1,a+m+1,[](node a,node b){
return a.t<b.t;
});
for(int i=1;i<m;i++)if(dist(make_pair(a[i].x,a[i].y),make_pair(a[i+1].x,a[i+1].y))>1ll*(a[i+1].t-a[i].t)*(a[i+1].t-a[i].t)){
cout<<n<<'\n';
return 0;
}
int ans=0;
for(int i=1;i<=n;i++){
int x=read(),y=read(),t=read();
auto it=mp.lower_bound(t);
bool fl=0;
if(it!=mp.end()){
if(dist(it->second,make_pair(x,y))>1ll*(it->first-t)*(it->first-t))fl=1;
}
it=mp.upper_bound(t);
if(it!=mp.begin()){
it=prev(it);
if(dist(it->second,make_pair(x,y))>1ll*(t-it->first)*(t-it->first))fl=1;
}
ans+=fl;
}
cout<<ans<<'\n';
return 0;
}
Moo Route II
好像是回了寝室之后做的,当时没有一遍读懂题意(leave
是多义词),听室友说才恍然大悟的。
比上面两题难一丢丢。主要是怎么实现“换乘时间 \(a_i\)”。一个自然的思路是对于一个 \((c,r,d,s)\),把 \(r\) 插到 \(c\) 的“出集合”里,\(s\) 插到 \(d\) 的“入集合”里,然后再放大每个点来看,把它的“入集合”在下面排成一条直线,“出集合”在上面排成一条直线,下面的时刻 \(t\) 往上面的 *st1[x].lower_bound(t+a[x])
连边。类似拆点的思路。然后正常跑 SPFA 就行了(有负权边)。话说不能用 Dij 的题出出来好像比较容易被 hack 吧……
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
register char ch=getchar();register int x=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
void print(ll x){
if(x<0){putchar('-');print(-x);return;}
if(x/10)print(x/10);
putchar(x%10+48);
}
const int N=2e5+5;
int n,m,tot;
vector<int>nods[N];
set<pair<int,int> >st1[N],st2[N];
vector<pair<int,int> >G[N*2];
ll dis[N*2];
bool inq[N*2];
queue<int>Q;
int main(){
n=read(),m=read();
for(int i=1,c,r,d,s;i<=m;i++){
c=read(),r=read(),d=read(),s=read();
st1[c].insert(make_pair(r,++tot)),st2[d].insert(make_pair(s,++tot));
G[tot-1].emplace_back(tot,s-r);
nods[c].emplace_back(tot-1),nods[d].emplace_back(tot);
}
for(int i=1,a;i<=n;i++){
a=read();
for(auto j:st2[i]){
auto it=st1[i].lower_bound(make_pair(j.first+a,0));
if(it!=st1[i].end()){
G[j.second].emplace_back(it->second,it->first-j.first);
}
}
for(auto it=st1[i].begin();it!=st1[i].end();it++){
if(next(it)!=st1[i].end())G[it->second].emplace_back(next(it)->second,next(it)->first-it->first);
}
for(auto it=st2[i].begin();it!=st2[i].end();it++){
if(next(it)!=st2[i].end())G[it->second].emplace_back(next(it)->second,next(it)->first-it->first);
}
}
memset(dis,0x3f,sizeof dis);
if(!st1[1].empty()){
G[++tot].emplace_back(st1[1].begin()->second,st1[1].begin()->first);
Q.push(tot),dis[tot]=0;inq[tot]=1;
}
while(!Q.empty()){
int x=Q.front();Q.pop();inq[x]=0;
for(auto y:G[x])if(dis[y.first]>dis[x]+y.second){
dis[y.first]=dis[x]+y.second;
if(!inq[y.first])Q.push(y.first),inq[y.first]=1;
}
}
for(int i=1;i<=n;i++){
ll D=dis[0];
for(int j:nods[i])D=min(D,dis[j]);
if(i==1)D=0;
print(D==dis[0]?-1ll:D),putchar('\n');
}
}
Gold
Equal Sum Subarrays
FJ gave Bessie an array \(a\) of length \(N(2≤N≤500,−10^{15}≤a_i≤10^{15})\) with all \(N(N+1)/2\) contiguous subarray sums distinct. For each index \(i∈[1,N]\), help Bessie compute the minimum amount it suffices to change \(a_i\) by so that there are two different contiguous subarrays of a with equal sum.
一开始写了个 \(N^3\log (N^2)\) 的,结果被卡了,发现可以通过预处理和双指针砍掉 \(\log\),于是过了。大概用了半个小时。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
ll a[505],s[505];
struct node {
int l,r;
ll s;
}t[250005],b[250005],c[250005];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)t[++m]=node{i,j,s[j]-s[i-1]};
sort(t+1,t+m+1,[](node a,node b){return a.s<b.s;});
for(int o=1;o<=n;o++){
int tot1=0,tot2=0;
for(int i=1;i<=m;i++){
if(t[i].r<o||t[i].l>o)b[++tot1]=t[i];
else c[++tot2]=t[i];
}
ll ans=8e18;
for(int i=1,j=0;i<=tot1;i++){
while(j<tot2&&c[j+1].s<=b[i].s)j++;
if(j)ans=min(ans,b[i].s-c[j].s);
if(j<tot2)ans=min(ans,c[j+1].s-b[i].s);
}
cout<<ans<<'\n';
}
}
Fertilizing Pastures
There are \(N\) pastures \((2≤N≤2⋅10^5)\), connected by \(N−1\) roads, such that they form a tree. Every road takes 1 second to cross. Each pasture starts out with 0 grass, and the \(i\)-th pasture's grass grows at a rate of \(a_i(1≤ai≤10^8)\) units per second. Farmer John is in pasture 1 at the start, and needs to drive around and fertilize the grass in every pasture. If he visits a pasture with \(x\) units of grass, it will need \(x\) amount of fertilizer. A pasture only needs to be fertilized the first time it is visited, and fertilizing a pasture takes 0 time.
The input contains an additional parameter \(T∈{0,1}\).
- If \(T=0\), Farmer John must end at pasture 1.
- If \(T=1\), Farmer John may end at any pasture.
Compute the minimum amount of time it will take to fertilize every pasture and the minimum amount of fertilizer needed to finish in that amount of time.
对于第一问(Minimum amount of time),一个输出 \(2(N-1)\) 一个输出 \(2(N-1)-mxdep\) 即可。
对于第二问:
当 \(T=0\) 时,设 \(f_i\) 表示 \(i\) 子树内的最少肥料,那么转移就是对于 \(f_v(v\in Son(i))\) 的一个排队接水问题,贪心 exchange-arguments 一下然后排个序转移即可。
当 \(T=1\) 时,还是把 \(v\in Son(i)\) 按 \(f_v\) 排个序,但是由于不好直接抉择哪个最后走(并不再上来),设 \(g_i\) 表示子树 \(i\) 最后走(不用回来)的最少肥料。那么如果是进入儿子 \(v(\rm{s.t.}mxdep_v=mxdep_i)\) 后不回来,就是把它拉到最后然后剩下的排队接水,预处理后缀 \(\sum w\) 就可以 \(O(1)\) 算拉到最后的影响。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=2e5+5;
int n,T,mxdep[N],dep[N],a[N],siz[N];
ll f[N],g[N],w[N];
vector<int>G[N];
void dfs1(int x,int p){
dep[x]=dep[p]+1,mxdep[x]=dep[x];
siz[x]=1,w[x]=a[x];
for(int y:G[x]){
dfs1(y,x);
siz[x]+=siz[y];
w[x]+=w[y];
mxdep[x]=max(mxdep[x],mxdep[y]);
}
sort(G[x].begin(),G[x].end(),[&](int i,int j){return siz[i]*w[j]<siz[j]*w[i];});
}
void dfs2(int x){
vector<ll>tmp;tmp.resize(G[x].size()+2);
int tot=0;
for(int y:G[x]){
dfs2(y);
f[x]+=f[y]+w[y];
f[x]+=tot*w[y];
tot+=2*siz[y];
}
for(int i=(int)G[x].size()-1;i>=0;i--)tmp[i]=tmp[i+1]+w[G[x][i]];
tot=0;g[x]=8e18;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(mxdep[y]==mxdep[x])g[x]=min(g[x],f[x]-f[y]-tot*w[y]-2*siz[y]*tmp[i+1]+2*(siz[x]-1-siz[y])*w[y]+g[y]);
tot+=2*siz[y];
}
if(G[x].empty())g[x]=0;
}
int main(){
n=read(),T=read();
for(int i=2;i<=n;i++)G[read()].emplace_back(i),a[i]=read();
dep[0]=-1;
dfs1(1,0),dfs2(1);
if(T==0){
cout<<(n-1)*2<<' '<<f[1]<<'\n';
}
else {
cout<<(n-1)*2-mxdep[1]<<' '<<g[1]<<'\n';
}
}
Piling Papers
Farmer John wrote down \(N(1≤N≤300)\) digits on pieces of paper. For each \(i∈[1,N]\), the \(i\)th piece of paper contains digit \(a_i (1≤a_i≤9)\).
The cows have two favorite integers \(A\) and \(B\) \((1≤A≤B<1018)\), and would like you to answer \(Q (1≤Q≤5⋅10^4)\) queries. For the \(i\)th query, the cows will move left to right across papers \(l_i…r_i (1≤l_i≤r_i≤N)\), maintaining an initially empty pile of papers. For each paper, they will either add it to the top of the pile, to the bottom of the pile, or neither. In the end, they will read the papers in the pile from top to bottom, forming an integer. Over all \(3^{r_i−l_i+1}\) ways for the cows to make choices during this process, count the number of ways that result in the cows reading an integer in \([A,B]\) inclusive, and output this number modulo \(10^9+7\).
首先这个 \(Q\) 比较大对做法有阻碍,想想发现可以离线下来然后枚举 \(L\),往右扫 \(R\) 的过程中区间 dp 把 \(R\) 设成一维即可实现离线查询。
根据 EA 的 Kaavi and Magic Spell 那题的套路,每个 "bottom/top" 操作序列可以看做一开始就确定所放数在最后序列的位置,那么每个时刻占用的是一段连续的区间,只要不越界就可以往两端相邻的扩展。(只用钦定第一次是 bottom
,系数是 \(2\);其他时候往两边都行,系数各是 \(1\)。)
对于这个区间 dp,我们关心的是最后选出的数是否在 \([A,B]\) 内,那么如果它的长度在 \([dig_A+1,dig_B-1]\) 肯定可以,这部分单独用 \(\sum_{dig_A<i<dig_B}{r-l+1\choose i}2^i\) 计算,如果它的长度 \(=dig_A\) 就要考虑是否 \(\ge A\),如果它的长度 \(=dig_B\) 就要考虑是否 \(\ge B\);特别地,如果 \(dig_A=dig_B\),则需要同时考虑和 \(A,B\) 的大小关系。
那么对于 \(dig_A<dig_B\) 的情况,考察往左/右添一个数,发现只关心原来的数是比 \(A\) 对应区间内的数字构成的数大、小还是等于,然后就可以推出新数是大于、小于还是等于。设 \(f(l,r,0/1/2)\) 表示当前填了 \([l,r]\),\(</=/>A[l,r]\),操作方案数。对于 \(dig_A=dig_B\) 的情况,设 \(f(l,r,0/1/2,0/1/2)\) 即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int mod=1e9+7;
int n,q,lenA,lenB,a[305],numA[20],numB[20],C[305][305],ans[50005],_2[305];
ll A,B;
vector<pair<int,int> >qu[305];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
namespace sub1{//len(A)<len(B),min lim
int f[305][19][19][3];//fix first as bottom, *2 when finished
void solve(){
for(int L=1;L<=n;L++){
memset(f,0,sizeof f);
for(int i=1;i<=lenA;i++)f[L-1][i][i-1][1]=1;
for(int i=L;i<=n;i++)for(int j=1;j<=lenA;j++)for(int k=j-1;k<=lenA;k++){
add(f[i][j][k][0],f[i-1][j][k][0]);
add(f[i][j][k][1],f[i-1][j][k][1]);
add(f[i][j][k][2],f[i-1][j][k][2]);
if(j<=k){
if(j<k){
add(f[i][j][k][a[i]==numA[j]?0:a[i]>numA[j]?2:0],f[i-1][j+1][k][0]);
add(f[i][j][k][a[i]==numA[j]?1:a[i]>numA[j]?2:0],f[i-1][j+1][k][1]);
add(f[i][j][k][a[i]==numA[j]?2:a[i]>numA[j]?2:0],f[i-1][j+1][k][2]);
}
add(f[i][j][k][0],f[i-1][j][k-1][0]*(1+(j==k)));
add(f[i][j][k][a[i]==numA[k]?1:a[i]>numA[k]?2:0],f[i-1][j][k-1][1]*(1+(j==k)));
add(f[i][j][k][2],f[i-1][j][k-1][2]*(1+(j==k)));
}
}
//if(L==1)cout<<f[1][1][1][1]<<',';
for(auto r:qu[L]){
int res=0;
for(int x=1;x<=2;x++)add(res,f[r.first][1][lenA][x]);
add(ans[r.second],res);
}
}
}
}
namespace sub2{//len(A)<len(B),max lim
int f[305][19][19][3];
void solve(){
for(int L=1;L<=n;L++){
memset(f,0,sizeof f);
for(int i=1;i<=lenB;i++)f[L-1][i][i-1][1]=1;
for(int i=L;i<=n;i++)for(int j=1;j<=lenB;j++)for(int k=j-1;k<=lenB;k++){
add(f[i][j][k][0],f[i-1][j][k][0]);
add(f[i][j][k][1],f[i-1][j][k][1]);
add(f[i][j][k][2],f[i-1][j][k][2]);
if(j<=k){
if(j<k){
add(f[i][j][k][a[i]==numB[j]?0:a[i]>numB[j]?2:0],f[i-1][j+1][k][0]);
add(f[i][j][k][a[i]==numB[j]?1:a[i]>numB[j]?2:0],f[i-1][j+1][k][1]);
add(f[i][j][k][a[i]==numB[j]?2:a[i]>numB[j]?2:0],f[i-1][j+1][k][2]);
}
add(f[i][j][k][0],f[i-1][j][k-1][0]*(1+(j==k)));
add(f[i][j][k][a[i]==numB[k]?1:a[i]>numB[k]?2:0],f[i-1][j][k-1][1]*(1+(j==k)));
add(f[i][j][k][2],f[i-1][j][k-1][2]*(1+(j==k)));
}
}
for(auto r:qu[L]){
int res=0;
for(int x=0;x<=1;x++)add(res,f[r.first][1][lenB][x]);
for(int i=lenA+1;i<lenB;i++)add(res,1ll*_2[i]*C[r.first-L+1][i]%mod);
add(ans[r.second],res);
}
}
}
}
namespace sub3{//len(A)==len(B),minlim&maxlim
int f[305][19][19][3][3];
void solve(){
for(int L=1;L<=n;L++){
memset(f,0,sizeof f);
for(int i=1;i<=lenA;i++)f[L-1][i][i-1][1][1]=1;
for(int i=L;i<=n;i++)for(int j=1;j<=lenA;j++)for(int k=j-1;k<=lenA;k++){
for(int x=0;x<=2;x++)for(int y=0;y<=2;y++)add(f[i][j][k][x][y],f[i-1][j][k][x][y]);
if(j<=k){
if(j<k){
for(int x=0;x<=2;x++)for(int y=0;y<=2;y++){
add(f[i][j][k][a[i]==numA[j]?x:a[i]>numA[j]?2:0][a[i]==numB[j]?y:a[i]>numB[j]?2:0],f[i-1][j+1][k][x][y]);
}
}
for(int x=0;x<=2;x++)for(int y=0;y<=2;y++){
add(f[i][j][k][x!=1?x:a[i]==numA[k]?x:a[i]>numA[k]?2:0][y!=1?y:a[i]==numB[k]?y:a[i]>numB[k]?2:0],f[i-1][j][k-1][x][y]*(1+(j==k))%mod);
}
}
}
for(auto r:qu[L]){
int res=0;
for(int x=1;x<=2;x++)for(int y=0;y<=1;y++)add(res,f[r.first][1][lenA][x][y]);
ans[r.second]=res;
}
}
}
}
int main(){
n=read();cin>>A>>B;
_2[0]=1;for(int i=1;i<=n;i++)_2[i]=2ll*_2[i-1]%mod;
C[0][0]=1;
for(int i=1;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
for(int i=1;i<=n;i++)a[i]=read();
for(ll t=A;t;t/=10)lenA++,numA[lenA]=t%10;
for(ll t=B;t;t/=10)lenB++,numB[lenB]=t%10;
reverse(numA+1,numA+lenA+1),reverse(numB+1,numB+lenB+1);
q=read();
for(int l,r,i=1;i<=q;i++){
l=read(),r=read();
qu[l].emplace_back(r,i);
}
if(lenA!=lenB)sub1::solve(),sub2::solve();
else sub3::solve();
for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
return 0;
}
/*
5 1 3278
2 3 4 1 5
3
1 4
2 5
1 5
*/
Platinum
Hungry Cows
由于情况形如前面的对后面的有影响,因此考虑楼房重建式线段树。
在时间轴上用线段树维护区间内的三个量:\(cnt,ans,more\),分别代表区间内有草堆吃的日子数、日子编号和、盈余的草堆数。并且,只有当草堆降落的日子也处在区间内时,该草堆降落的日子贡献的草堆才会被计入 \(cnt\) 和 \(ans\)! 我们遵循一个原则:先吃新来的草堆,后吃前面盈余的草堆。
pushup
时,尝试用左儿子的 \(more\) 填充右儿子的区间内留空的位置,但不真正填入。我们用 node upd(v,l,r,k)
模拟用左边的 \(v\) 个草堆填充线段树上 \((l,r,k)\) 节点的区间的过程,它会返回一组 \(cnt',ans',more'\) 表示填充完后的各个量。将左儿子与这个结构体合并即可实现 pushup
。
再来考虑 upd
,若到了一个节点,传过来的盈余值为 \(v\):如果 \(v\) 能够填满左儿子的空位数 \(vac\),则递归用 \(v-vac+more_{ls}\) upd
右儿子;否则,我们用 \(v\) 递归 upd
左儿子,我们还想用 \(more_{ls}\) 递归 upd
右儿子,然而这是不行的,复杂度爆炸,但是由于这部分可以用之前的信息——\(ans_k-ans_{ls}\)、\(cnt_k-cnt_{ls}\),所以复杂度还是对的。调用一次 upd
复杂度 \(O(\log V)\)。
于是总复杂度就是 \(O(q\log^2 V)\) 的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
ll x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=1e5+5,mod=1e9+7;
const ll lim=2e14;
int tot;
struct node {
int ls,rs,ans;
ll cnt,more;
}t[N*55];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
inline void sub(int &x,int y){(x-=y)<0&&(x+=mod);}
node pushup(node a,node b){
node ret;
ret.cnt=ret.ans=ret.more=0;
ret.cnt=a.cnt+b.cnt;
ret.ans=(a.ans+b.ans)%mod;
ret.more=b.more;
return ret;
}
node upd(ll v,ll l,ll r,int &k){
if(!k){
node ret;
ret.cnt=min(v,r-l+1);
ret.ans=(l+min(r,l+v-1))%mod*(min(v,r-l+1)%mod)%mod*((mod+1)/2)%mod;
ret.more=max(0ll,v-(r-l+1));
return ret;
}
if(!v)return t[k];
if(l==r){
node ret;
ret.cnt=ret.ans=ret.more=0;
if(v){
if(t[k].cnt)ret.more=t[k].more+v,ret.cnt=1,ret.ans=l%mod;
else ret.more=v-1,ret.cnt=1,ret.ans=l%mod;
}
else ret=t[k];
return ret;
}
node ret;
ll mid=l+r>>1;
ll vac=mid-l+1-t[t[k].ls].cnt;
if(vac>v)return ret=upd(v,l,mid,t[k].ls),ret.cnt-=t[t[k].ls].cnt,sub(ret.ans,t[t[k].ls].ans),ret.cnt+=t[k].cnt,add(ret.ans,t[k].ans),ret.more=t[k].more,ret;
else {
ret.cnt=mid-l+1,ret.ans=((l+mid)%mod)*((mid-l+1)%mod)%mod*((mod+1)/2)%mod,ret.more=0;
return pushup(ret,upd(t[t[k].ls].more+v-vac,mid+1,r,t[k].rs));
}
}
void chg(ll p,ll v,ll l,ll r,int &k){
if(!k)k=++tot;
if(l==r){
if(v)t[k].cnt=1,t[k].ans=p%mod,t[k].more=v-1;
else t[k].cnt=t[k].ans=t[k].more=0;
return;
}
ll mid=l+r>>1;
if(p<=mid){
chg(p,v,l,mid,t[k].ls);
node tmp=pushup(t[t[k].ls],upd(t[t[k].ls].more,mid+1,r,t[k].rs));
t[k].ans=tmp.ans,t[k].cnt=tmp.cnt,t[k].more=tmp.more;
}
else {
chg(p,v,mid+1,r,t[k].rs);
node tmp=pushup(t[t[k].ls],upd(t[t[k].ls].more,mid+1,r,t[k].rs));
t[k].ans=tmp.ans,t[k].cnt=tmp.cnt,t[k].more=tmp.more;
}
}
signed main(){
int T,Rt=0;
T=read();
while(T--){
ll p=read(),h=read();
chg(p,h,1,lim,Rt);
cout<<t[Rt].ans<<'\n';
}
}
Problem Setting
考场上有了基本思路,但是没能正确实现半在线卷积,而且 FWT 和子集卷积忘得差不多了。
首先问题显然可以等价成,有 \(2^m\) 个点,每个点 \(0\le S<2^m\) 上写着一个数 \(cnt_S\) 表示数 \(S\) 出现了 \(cnt_S\) 次,所有 \(T(T\subset S)\) 向 \(S\) 连有向边,要求求出有多少个点的序列,满足能够按照这个顺序依次经过每个点(可以重复)。那么转移式显然是 \(f_S=(1+\sum_{T\subset S}f_T)\times g_{cnt_S}\),其中 \(g_x\) 表示有 \(x\) 个不同的球,依次 选出 若干个 的方案数(不能不选)。
先来考虑 \(g_x\) 的递推方法。这是经典问题。由于排列数 \(A_x^y=x\cdot A_{x-1}^{y-1}\),而 \(g_x\) 实际上是 \(\sum_{y=1}^x A_x^y\),所以有 \(g_x=x\cdot (1+g_{x-1})\)(加的 \(1\) 是没算的 \(A_{x-1}^0\))。
然后考虑优化 \(f_S\) 的 \(O(3^n)\) 暴力转移。考虑一个半在线 FWT,每次加入所有 \(|S|=i\) 的 \(f_S\) 并重新计算高维前缀和,如下:
for(int i=0;i<=m;i++){
for(int j=0;j<(1<<m);j++){
if(ppc[j]==i)f[j]=(1ll+h[j])*g[cnt[j]]%mod,add(ans,f[j]);
else f[j]=0;
}
for(int w=1;w<(1<<m);w<<=1) //解卷
for(int j=0;j<(1<<m);j+=w<<1)
for(int k=0;k<w;k++)
add(h[j+k+w],mod-h[j+k]);
for(int j=0;j<(1<<m);j++)add(h[j],f[j]); //修修补补
for(int w=1;w<(1<<m);w<<=1) //卷回去
for(int j=0;j<(1<<m);j+=w<<1)
for(int k=0;k<w;k++)
add(h[j+k+w],h[j+k]);
}
复杂度是 \(O(2^mm^2)\)。
Watching Cowflix
并没有看懂 \(\text{polylog}\) 做法,订了个根号。
考虑根号分治,对于较小的 \(k(k\le\sqrt n)\),暴力树上连通块 dp 即可;对于 \(k>\sqrt n\),一个比较优美的做法是分治,伪代码如下:
void solve(int l,int r){//表示开区间 (l,r)
if(r-l<2)return;
if(ansg[l]==ansg[r]){//ansg[i] 表示 K=i 时所用连通块数量,ansf[i] 表示代价
将 ansg[l...r] 分别修改为 ansg[l],并把 ansf[l...r] 修改为对应的值(可以平凡计算)
return;
}
int mid=l+r>>1;
暴力 dfs 求出 ansg[mid],ansf[mid]
solve(l,mid),solve(mid,r);
}