前言
我就想說一句,T3 給了一個什麼牛馬大樣例!!!!!!!!,氣\(^{TM}\)死我!!!!!!!
我的 \(\mathcal{O}(n)\) 演算法始終過不掉大樣例我 TM ,要不然我就直接上矩陣乘優化了。。
最後沒法了把我的所謂的不正確的正解交上去,考完發現有 56pts ,我以為是資料水,果斷去看正解。。
苦思冥想了好久直到戰神大樣例沒過切掉了此題,我才開始矩陣乘優化,然後就過了(然而和題解柿子完全不一樣。。)
牛馬大樣例!!!!
T1 自然數
解題思路
考場上一眼 CDQ 分治,最後一看果然不是。。(逃
可惜的是我打的小暴力還被卡常卡掉了 34pts 。。。
對於每一個左端點線段樹維護出每一個右端點的和,因此我們先求出左端點為 1 的所有答案作為初始值加入到線段樹中。
然後向右移動左端點,每次相當於刪去一個值 \(x\) 那麼在當前掃到的左端點到 \(x\) 的上一次出現位置的所有大於 \(x\) 的答案都會變為 \(x\) 。
由於答案的值是單調不降的,因此我們可以二分答案線上段樹上面查值,也可以線段樹上二分,複雜度帶一個或者兩個 log
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e5+10;
int n,ans,nxt[N],s[N],cnt[N],num[N];
unordered_map<int,int> mp;
struct Segment_Tree
{
struct Node{int mn,dat,laz;}tre[N<<2];
void push_up(int x)
{
tre[x].mn=min(tre[ls].mn,tre[rs].mn);
tre[x].dat=tre[ls].dat+tre[rs].dat;
}
void push_down(int x,int l,int r)
{
if(tre[x].laz==-1) return ;
int mid=(l+r)>>1;
tre[ls].laz=tre[x].laz; tre[rs].laz=tre[x].laz;
tre[ls].dat=(mid-l+1)*tre[x].laz; tre[rs].dat=(r-mid)*tre[x].laz;
tre[ls].mn=tre[rs].mn=tre[x].laz;
tre[x].laz=-1;
}
void build(int x,int l,int r)
{
tre[x].laz=-1;
if(l==r) return tre[x].mn=tre[x].dat=num[l],void();
int mid=(l+r)>>1;
build(ls,l,mid); build(rs,mid+1,r);
push_up(x);
}
void insert(int x,int l,int r,int L,int R,int val)
{
if(L<=l&&r<=R) return tre[x].dat=val*(r-l+1),tre[x].laz=tre[x].mn=val,void();
int mid=(l+r)>>1; push_down(x,l,r);
if(L<=mid) insert(ls,l,mid,L,R,val);
if(R>mid) insert(rs,mid+1,r,L,R,val);
push_up(x);
}
int query(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x].dat;
int mid=(l+r)>>1,sum=0; push_down(x,l,r);
if(L<=mid) sum+=query(ls,l,mid,L,R);
if(R>mid) sum+=query(rs,mid+1,r,L,R);
return sum;
}
int query(int x,int l,int r,int pos)
{
if(l==r) return tre[x].mn;
int mid=(l+r)>>1; push_down(x,l,r);
if(pos<=mid) return query(ls,l,mid,pos);
return query(rs,mid+1,r,pos);
}
}T;
signed main()
{
freopen("mex.in","r",stdin); freopen("mex.out","w",stdout);
n=read(); for(int i=1;i<=n;i++) s[i]=read();
for(int i=1,pos=0;i<=n;i++)
{
if(s[i]<=n) cnt[s[i]]++;
while(cnt[pos]) pos++;
num[i]=pos;
}
T.build(1,1,n);
for(int i=n;i;i--)
{
nxt[i]=n+1;
if(mp.find(s[i])==mp.end()){mp.insert(make_pair(s[i],i));continue;}
nxt[i]=mp.find(s[i])->second; mp[s[i]]=i;
}
for(int i=1;i<=n;i++)
{
ans+=T.query(1,1,n,i,n);
int l=i,r=nxt[i]-1,temp=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(T.query(1,1,n,mid)>s[i]) temp=mid,r=mid-1;
else l=mid+1;
}
if(~temp) T.insert(1,1,n,temp,nxt[i]-1,s[i]);
}
printf("%lld",ans);
return 0;
}
T2 錢倉
解題思路
顯然的一個結論一定存在一個點,使得按照順時針順序原來在它之前的點上的錢一定不會運到在它之後的點上。
於是我們就可以得到列舉斷點求答案的 \(\mathcal{O}(n^2)\) 做法。
然後發現所有合法的序列其實答案都是相同的,我是我們可以列舉 \([1,2n]\) 檢視是否有長度為 \(n\) 的一段。
滿足一段內距離左端點的字首和與長度的差值始終大於等於 0。
直接從該斷點計算即可。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e5+10,INF=1e18;
int n,ans=INF,beg,s[N],p[N];
void solve(int fro)
{
for(int i=fro,temp=0;i<=fro+n-1;i++)
{
temp+=s[i]-1; p[i-fro+1]=s[i];
if(temp<0) return ;
}
int sum=0,pos=1; p[pos]--;
for(int i=2;i<=n;i++)
{
while(!p[pos]) pos++;
p[pos]--; sum+=(i-pos)*(i-pos);
}
ans=min(ans,sum);
}
signed main()
{
freopen("barn.in","r",stdin); freopen("barn.out","w",stdout);
n=read(); for(int i=1;i<=n;i++) s[i+n]=s[i]=read();
for(int i=1,sum=0;i<=2*n;i++)
{
sum+=s[i];
if(sum-i+beg<0) beg=i,sum=0;
if(i-beg==n) break;
}
solve(beg+1); printf("%lld",ans);
return 0;
}
T3 遊戲
解題思路
如果沒有那個大樣例的話這絕對稱得上是個期望好題。
易得,在只剩下一個棋子的時候都希望自己可以拿走,那麼在剩下兩個石子的時候一定都希望自己下一局先手,從而不希望本局可以取到石子。
以此類推,可以發現剩下奇數個石子的時候兩人都想到得到,偶數時則相反。
假設這一局 \(A\) 先手的概率是 \(lasa\) , \(B\) 先手的概率是 \(lasb\) ,下一局的概率是 \(A,B\) 。
如果是剩下奇數個石子時,轉移就是:
對於偶數的情況大概類似,只不過變成了兩者都不想要。。
優化的話直接矩陣乘法就好了,由於矩陣乘法 不滿足交換率 因此我們應該把一奇一偶綁在一起成為一個新的矩陣進行快速冪,如果是奇數最後再乘一次奇數矩陣就好了。。。
當然,總有些神仙和常人的思維不同,有意者可以去 zxb 的 blog 學習倍增演算法。。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int mod=1e9+7,Inv=571428574;
int T,n,a,b;
struct Square
{
int a[2][2];
void clear(){memset(a,0,sizeof(a));}
Square friend operator * (Square x,Square y)
{
Square z; z.clear();
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
z.a[i][j]+=x.a[i][k]*y.a[k][j];
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
z.a[i][j]%=mod;
return z;
}
}A,B,C,ans;
int power(int x,int y,int p=mod){int temp=1;while(y){if(y&1) temp=temp*x%p;x=x*x%p; y>>=1;}return temp;}
void pw(Square &x,Square y,int po){while(po){if(po&1) x=x*y;y=y*y; po>>=1;}}
void solve()
{
n=read(); a=read()*Inv%mod; b=read()*Inv%mod;
int sum=(a+(1-a+mod)*b)%mod,lasa=1,lasb=0;
int inv=power((a+b-a*b%mod+mod)%mod,mod-2);
A.clear(); B.clear(); C.clear(); ans.clear();
ans.a[0][1]=1;
A.a[0][0]=(1-a+mod)*b%mod*inv%mod;
A.a[0][1]=b*inv%mod;
A.a[1][0]=a*inv%mod;
A.a[1][1]=(1-b+mod)*a%mod*inv%mod;
inv=power((1-a*b%mod+mod)%mod,mod-2);
B.a[0][0]=a*(1-b+mod)%mod*inv%mod;
B.a[0][1]=(1-b+mod)*inv%mod;
B.a[1][0]=(1-a+mod)*inv%mod;
B.a[1][1]=b*(1-a+mod)%mod*inv%mod;
C=A*B; pw(ans,C,n/2);
if(n&1) ans=ans*A;
printf("%lld\n",ans.a[0][0]%mod);
}
signed main()
{
freopen("game.in","r",stdin); freopen("game.out","w",stdout);
T=read(); while(T--) solve();
return 0;
}