2021牛客暑期多校训练营1
Alice and Bob
SG打表:
通过SG打表发现他大部分情况都是先手必胜态。
递推转移:
所以我们可以通过递推的方式,将先手必败态转移必胜,之后先手必胜被标记过后将不再转移。即可将时间复杂优化为 O ( n 2 ∗ l o g n ) O(n^2*logn) O(n2∗logn)
或者直接打表 (雾
代码:
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N=5e3+10;
int t,n,m;
int sg[N][N];
void SG(){
for(int i=0;i<=5000;i++){
for(int j=i;j<=5000;j++){
if(!sg[i][j]){
for(int s=1;i+s<=5000;s++){
for(int k=0;j+k*s<=5000;k++){
int a=i+s,b=j+k*s;
if(a>b) swap(a,b);
sg[a][b]=1;
}
}
for(int s=1;j+s<=5000;s++){
for(int k=0;i+k*s<=5000;k++){
int a=j+s,b=i+k*s;
if(a>b) swap(a,b);
sg[a][b]=1;
}
}
}
}
}
}
int main(){
SG();
scanf("%d",&t);
while(t--){
scanf("%d %d",&n,&m);
if(n>m) swap(n,m);
if(sg[n][m]) cout<<"Alice\n";
else cout<<"Bob\n";
}
}
Ball Dropping
几何题,队友写的,^^_
Determine the Photo Position
签到题,模拟即可。
Find 3-friendly Integers
数位DP
这题上来一看就感觉是数位DP,首先发现能被3整除的,其数位和也能被3整除,记录一个数位的前缀和,在维护前缀和膜3为0,1,2的个数,即可判断数字的子串能否被3整除。
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int t,tot,a[20];
ll l,r,dp[20][3][3][3][3][2][2];
ll dfs(int p,int cnt0,int cnt1,int cnt2,int s,int pre,int lim){
if(p==0){
if(cnt0==2||cnt1==2||cnt2==2) return 1;
return 0;
}
if(dp[p][cnt0][cnt1][cnt2][s][pre][lim]!=-1) return dp[p][cnt0][cnt1][cnt2][s][pre][lim];
int up=lim? a[p]:9;
ll ans=0;
for(int i=0;i<=up;i++){
if(pre&&i==0){
ans+=dfs(p-1,cnt0,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
}else{
if(cnt0<2&&(s+i)%3==0){
ans+=dfs(p-1,cnt0+1,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
}else if(cnt1<2&&(s+i)%3==1){
ans+=dfs(p-1,cnt0,cnt1+1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
}else if(cnt2<2&&(s+i)%3==2){
ans+=dfs(p-1,cnt0,cnt1,cnt2+1,(s+i)%3,pre&&i==0,lim&&i==up);
}else{
ans+=dfs(p-1,cnt0,cnt1,cnt2,(s+i)%3,pre&&i==0,lim&&i==up);
}
}
}
return dp[p][cnt0][cnt1][cnt2][s][pre][lim]=ans;
}
ll solve(ll x){
int tot=0;
memset(dp,-1,sizeof dp);
while(x){
a[++tot]=x%10;
x/=10;
}
return dfs(tot,1,0,0,0,1,1);
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%lld %lld",&l,&r);
printf("%lld\n",solve(r)-solve(l-1));
}
}
Determine the Photo Position
贪心
神仙贪心题,严格证明看官方题解。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFfqo509-1629730995483)(C:\Users\5\AppData\Roaming\Typora\typora-user-images\image-20210721195857947.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0owNqeTD-1629730995486)(C:\Users\5\AppData\Roaming\Typora\typora-user-images\image-20210721195909797.png)]
代码:
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+7;
int n,k;
ll a[N],b[N],mx[N],mn[N];
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++){
scanf("%lld",&b[i]);
}
if(n==2){
k=k&1;
if(k) swap(a[1],a[2]);
printf("%lld\n",abs(a[1]-b[1])+abs(a[2]-b[2]));
return 0;
}
ll ans=0;
for(int i=1;i<=n;i++){
ans+=abs(a[i]-b[i]);
mx[i]=max(a[i],b[i]);
mn[i]=min(a[i],b[i]);
}
sort(mx+1,mx+1+n);
sort(mn+1,mn+1+n);reverse(mn+1,mn+1+n);
//for(int i=1;i<=n;i++) cout<<mn[i]<<" "<<mx[i]<<endl;
for(int i=1;i<=min(n,k);i++){
if(mn[i]>mx[i])
ans+=mn[i]-mx[i] <<1ll;
}
printf("%lld\n",ans);
}
Hash Function
比赛时被我们水过去了,正解貌似是FFT或NTT,等我补坑^^_
Increasing Subsequence
概率DP
这题一看就是概率DP
状态划分
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k],当前Alice和Bob上次所选位置为 i i i和 j j j,且是k=[0/1]为当前是Alice或Bob。
计算期望
d p [ i ] [ j ] [ 0 ] = ∑ d p [ i ] [ k ] [ 1 ] ∗ p + 1 dp[i][j][0]=\sum dp[i][k][1]*p+1 dp[i][j][0]=∑dp[i][k][1]∗p+1, a [ k ] > a [ i ] , k > j a[k]>a[i],k>j a[k]>a[i],k>j
d p [ i ] [ j ] [ 1 ] = ∑ d p [ k ] [ j ] [ 0 ] ∗ p + 1 dp[i][j][1]=\sum dp[k][j][0]*p+1 dp[i][j][1]=∑dp[k][j][0]∗p+1, a [ k ] > a [ j ] , k > i a[k]>a[j],k>i a[k]>a[j],k>i
其中 p p p为事件发生的概率。即后面能选的数量。
之后递归来实现即可,但是这是这还远远不够,因为此时的复杂度,我们还需要对其优化。
递推优化
我们能将 d f s dfs dfs里面要乘上的概率放到 d f s dfs dfs外面,然后在里面每次只 + 1 +1 +1的转移,即可将 d f s dfs dfs里面的 f o r for for循环优化掉。
优化前代码:
#include <iostream>
using namespace std;
typedef long long ll;
const int N=5007;
const ll mod=998244353;
int n,a[N];
ll inv[N],c[N][N];
ll dp[N][N][2];
ll ksm(ll x,ll p){
ll res=1;
while(p){
if(p&1) res=res*x%mod;
p>>=1;
x=x*x%mod;
}
return res;
}
ll dfs(int p1,int p2,int s){
if(dp[p1][p2][s]) return dp[p1][p2][s];
ll ans=0;
if(s==0){
int cnt=c[p2+1][a[p1]];
for(int i=p2+1;i<=n;i++){
if(a[i]>a[p1])
ans=(ans+(dfs(p1,i,1)+1)*inv[cnt]%mod)%mod;
}
}else{
int cnt=c[p1+1][a[p2]];
for(int i=p1+1;i<=n;i++){
if(a[i]>a[p2])
ans=(ans+(dfs(i,p2,0)+1)*inv[cnt])%mod;
}
}
return dp[p1][p2][s]=ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
inv[i]=ksm(i,mod-2);
}
for(int i=n;i>=1;i--){
for(int j=1;j<=n;j++){
c[i][j]=c[i+1][j];
if(j<a[i]) c[i][j]++;
}
}
ll res=0;
for(int i=1;i<=n;i++){
res=(res+(dfs(i,0,0)+1)*inv[n]%mod)%mod;
}
printf("%lld\n",res);
}
优化后代码
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=5002;
const ll mod=998244353;
int n,a[N],c[N][N];
int inv[N];
int dp[N][N][2];
ll ksm(ll x,ll p){
ll res=1;
while(p){
if(p&1) res=res*x%mod;
p>>=1;
x=x*x%mod;
}
return res;
}
ll qu(ll x){
ll cnt=x/mod;
x-=cnt*mod;
return x;
}
ll dfs(int p1,int p2,int s){
if(p1>n||p2>n) return 0;
if(~dp[p1][p2][s]) return dp[p1][p2][s];
ll ans=0;
if(s==0){
int cnt=c[p1+1][a[p2]];
if(a[p2]>a[p1]){
ans=qu(ans+qu(qu(dfs(p1+1,p2,1)*inv[cnt])+1ll));
}
ans=qu(ans+dfs(p1,p2+1,0));
}else{
int cnt=c[p2+1][a[p1]];
if(a[p1]>a[p2]){
ans=qu(ans+qu(qu(dfs(p1,p2+1,0)*inv[cnt])+1ll));
}
ans=qu(ans+dfs(p1+1,p2,1));
}
return dp[p1][p2][s]=ans;
}
int main(){
scanf("%d",&n);
memset(dp,-1,sizeof dp);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
inv[i]=ksm(i,mod-2);
}
for(int i=n;i>=1;i--){
for(int j=1;j<=n;j++){
c[i][j]=c[i+1][j];
if(j<a[i]) c[i][j]++;
}
}
ll res=0;
for(int i=1;i<=n;i++){
int cnt=c[1][a[i]];
res=qu(res+qu((qu(dfs(i,1,0)*inv[cnt])+1ll)*inv[n]));
}
printf("%lld\n",res);
}
Journey among Railway Stations
思路:
维护到该点到下个节点的最早时间 b t bt bt,该点最晚时间 e d ed ed,到下个点的花费 c c c,能否到该点 o k ok ok用线段树来维护各个值即可
代码
#include <iostream>
#define lch (k<<1)
#define rch (k<<1|1)
#define mid (l+r>>1)
using namespace std;
typedef long long ll;
const int N=1e6+7;
int T,n,m;
struct node{ll bt,ed,c,ok;}tr[4*N];
int a[N],b[N],c[N];
node merge(node x,node y){
node z;
z.ok=x.ok&y.ok;
if(x.bt>y.ed) z.ok=0;
z.c=x.c+y.c;
z.bt=max(x.bt+y.c,y.bt);
z.ed=min(x.ed,y.ed-x.c);
return z;
}
void pushup(int k){
tr[k]=merge(tr[lch],tr[rch]);
}
void init(int k,int l,int r){
if(l==r){
tr[k].bt=(ll)a[l]+c[l];
tr[k].ed=b[l];
tr[k].c=c[l];
tr[k].ok=1;
return;
}
init(lch,l,mid);
init(rch,mid+1,r);
pushup(k);
}
void update(int k,int l,int r,int p,node v){
if(l==r){
tr[k]=v;
return;
}
if(p<=mid) update(lch,l,mid,p,v);
else update(rch,mid+1,r,p,v);
pushup(k);
}
node query(int k,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){
return tr[k];
}
if(qr<=mid) return query(lch,l,mid,ql,qr);
else if(ql>mid) return query(rch,mid+1,r,ql,qr);
else return merge(query(lch,l,mid,ql,qr),query(rch,mid+1,r,ql,qr));
}
void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<n;i++) scanf("%d",&c[i]);
init(1,1,n);
scanf("%d",&m);
while(m--){
int op;
scanf("%d",&op);
if(op==0){
//int l r;
int l,r;
scanf("%d %d",&l,&r);
int ok=query(1,1,n,l,r).ok;
if(ok) puts("Yes");
else puts("No");
}else if(op==1){
int x,w;
scanf("%d %d",&x,&w);
node t=query(1,1,n,x,x);
c[x]=w;
t.bt=(ll)a[x]+w;
t.c=w;
update(1,1,n,x,t);
}else{
int x,p,q;
scanf("%d %d %d",&x,&p,&q);
node t=query(1,1,n,x,x);
a[x]=p,b[x]=q;
t.bt=(ll)p+t.c,t.ed=q;
update(1,1,n,x,t);
}
}
}
int main(){
scanf("%d",&T);
while(T--) solve();
}
Knowledge Test about Match
待补^^_