NOI2017 部分題解
D1
T1
模擬一個很大的二進位制加減法
壓位線段樹,每個位置壓二進位制的30位,每次修改涉及1~2個位置,分別修改
對於一個位置i +/-,至多產生1個進/退位,相當於在i+1~inf +/- 1,找到>=i的位置中最小的非1/0位,中間的全部改0/1,這一位+/-1
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
inline void read(int &x)
{
char c; int f=1; while(!((c=getchar())>='0'&&c<='9')) if(c=='-') f=-f;
x=c-'0';
while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
x*=f;
}
const int maxn = 2100000;
int n,m;
int seg[maxn<<2];
void pushdown(const int x) { if(seg[x]!=-1) seg[x<<1]=seg[x<<1|1]=seg[x]; }
void pushup(const int x) { seg[x]=seg[x<<1]==seg[x<<1|1]?seg[x<<1]:-1; }
int loc;
int search(const int x,const int l,const int r)
{
if(l==r) return x;
pushdown(x);
int mid=l+r>>1;
if(loc<=mid) search(x<<1,l,mid);
else search(x<<1|1,mid+1,r);
}
int lx,rx,c;
int find0(const int x,const int l,const int r)
{
if(r<lx||seg[x]==(1<<30)-1) return -1;
if(l==r) { seg[x]++; rx=r-1; return 1; }
pushdown(x);
int mid=l+r>>1;
int re=find0(x<<1,l,mid);
if(re!=-1) { pushup(x); return re; }
re=find0(x<<1|1,mid+1,r); pushup(x);
return re;
}
int find1(const int x,const int l,const int r)
{
if(r<lx||seg[x]==0) return -1;
if(l==r) { seg[x]--; rx=r-1; return 1; }
pushdown(x);
int mid=l+r>>1;
int re=find1(x<<1,l,mid);
if(re!=-1) { pushup(x); return re; }
re=find1(x<<1|1,mid+1,r); pushup(x);
return re;
}
void upd(const int x,const int l,const int r)
{
if(rx<l||r<lx) return;
if(lx<=l&&r<=rx) { seg[x]=c; return; }
pushdown(x);
int mid=l+r>>1;
upd(x<<1,l,mid); upd(x<<1|1,mid+1,r);
pushup(x);
}
void add(const int k,const int v)
{
loc=k; int x=search(1,0,n);
seg[x]+=v; int ci=seg[x]>=(1<<30)?1:0;
seg[x]&=(1<<30)-1;
while(x) x>>=1,pushup(x);
if(ci)
{
lx=k+1; find0(1,0,n);
c=0; if(lx<=rx) upd(1,0,n);
}
}
void dec(const int k,const int v)
{
loc=k; int x=search(1,0,n);
seg[x]-=v; int ci=seg[x]<0?1:0;
if(seg[x]<0) seg[x]+=1<<30;
while(x) x>>=1,pushup(x);
if(ci)
{
lx=k+1; find1(1,0,n);
c=(1<<30)-1; if(lx<=rx) upd(1,0,n);
}
}
int query(const int k)
{
loc=k/30; int x=search(1,0,n);
return seg[x]>>k-loc*30&1;
}
int main()
{
scanf("%d%*d%*d%*d",&m); n=m+2;
while(m--)
{
int t,x,y; read(t);
if(t==1)
{
read(x); read(y);
int sig=1; if(x<0) sig=-1,x=-x;
int d=y/30*30,u=d+30;
int cc=0;
for(int i=0;i<30&&y+i<u;i++) if(x>>i&1) cc|=1<<i+y-d;
if(cc)
{
if(sig==1) add(d/30,cc);
else dec(d/30,cc);
}
cc=0;
for(int i=u-y;i<30;i++) if(x>>i&1) cc|=1<<i-(u-y);
if(cc)
{
if(sig==1) add(u/30,cc);
else dec(u/30,cc);
}
}
else read(x),printf("%d\n",query(x));
}
return 0;
}
T2
我沒過這題…卡常卡了一個上午沒卡過去…深深的怨念
因為詢問的字串長度k<=50,實際上我們只關心長度<=k的串的出現次數,每次合併/分開兩個串,至多影響k^2個串的出現次數
所以離線,hash表維護詢問的所有子串的出現次數,雙hash
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
inline void read(int &x)
{
char c; while(!((c=getchar())>='0'&&c<='9'));
x=c-'0';
while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
int oo[10],on;
inline void output(int x)
{
if(!x) { puts("0");return; }
while(x)
{
int y=x/10; oo[++on]=x-y*10;
x=y;
}
while(on) putchar('0'+oo[on--]);
putchar('\n');
}
const int hash1 = 23333333;
//const int hash2 = 1e8+7;
const int Mod = 998244353;
const int maxk = 51;
const int maxn = 210000;
const int maxm = 510000;
const int maxs = 1e7+10;
int ph1[maxk+10],ph2[maxk+10];
int s1[maxs],s2[maxs];
void gethash(int x,int y,int &h1,int &h2)
{
h1=(s1[y]-(ll)s1[x-1]*ph1[y-(x-1)]%hash1+hash1)%hash1;
h2=s2[y]-(ll)s2[x-1]*ph2[y-(x-1)];
}
struct edge{int y,c;};
vector<edge>V[hash1]; int sum[maxk];
int fi(int h1,int h2)
{
for(int j=0;j<V[h1].size();j++)
if(V[h1][j].y==h2) return V[h1][j].c;
}
void ins(int h1,int h2)
{
for(int j=0;j<V[h1].size();j++) if(V[h1][j].y==h2) return;
V[h1].push_back((edge){h2,0});
}
void upd(int h1,int h2,int c)
{
for(int j=0;j<V[h1].size();j++)
if(V[h1][j].y==h2) { V[h1][j].c+=c; return; }
}
int n,m;
struct list{int l,r,x;}a[maxn];
int temp[maxk<<2],L,R;
void link(const int x,const int y)
{
int nowi=x; for(L=maxk;nowi&&maxk-L+1<50;L--,nowi=a[nowi].l) temp[L]=a[nowi].x; L++;
nowi=y; for(R=maxk+1;nowi&&R-maxk<50;R++,nowi=a[nowi].r) temp[R]=a[nowi].x; R--;
s1[L-1]=s2[L-1]=0;
for(int i=L;i<=R;i++)
{
s1[i]=s1[i-1]*11+temp[i]; if(s1[i]>=hash1) s1[i]%=hash1;
s2[i]=s2[i-1]*23+temp[i];
}
for(int l=2;l<maxk;l++) if(sum[l])
{
for(int i=max(maxk+2-l,L);i<=maxk&&i+l-1<=R;i++)
{
int h1,h2; gethash(i,i+l-1,h1,h2);
upd(h1,h2,1);
}
}
a[x].r=y; a[y].l=x;
}
void cut(const int x)
{
int y=a[x].r;
int nowi=x; for(L=maxk;nowi&&maxk-L+1<50;L--,nowi=a[nowi].l) temp[L]=a[nowi].x; L++;
nowi=y; for(R=maxk+1;R-maxk<50&&nowi;R++,nowi=a[nowi].r) temp[R]=a[nowi].x; R--;
s1[L-1]=s2[L-1]=0;
for(int i=L;i<=R;i++)
{
s1[i]=s1[i-1]*11+temp[i]; if(s1[i]>=hash1) s1[i]%=hash1;
s2[i]=s2[i-1]*23+temp[i];
}
for(int l=2;l<maxk;l++) if(sum[l])
{
for(int i=max(maxk+2-l,L);i<=maxk&&i+l-1<=R;i++)
{
int h1,h2; gethash(i,i+l-1,h1,h2);
upd(h1,h2,-1);
}
}
a[x].r=0; a[y].l=0;
}
char str[maxs];
int e[maxm][3];
struct node{int h1,h2;};
vector<node>q[maxm];
int main()
{
ph1[0]=ph2[0]=1;
for(int i=1;i<maxk+10;i++) ph1[i]=(ll)ph1[i-1]*11%hash1,ph2[i]=(ll)ph2[i-1]*23;
read(n); read(m);
for(int i=1;i<=n;i++) read(a[i].x),a[i].l=a[i].r=0;
for(int i=1;i<=m;i++)
{
int t; read(t);
if(t==1) e[i][0]=1,read(e[i][1]),read(e[i][2]);
else if(t==2) e[i][0]=2,read(e[i][1]);
else
{
e[i][0]=3;
scanf("%s",str+1); int len=strlen(str+1);
int k; read(k); sum[e[i][1]=k]=1;
int ss00=0,ss01=0,ss10=0,ss11=0;
for(int j=1;j<=k;j++)
{
ss01=ss01*11+str[j]-48; if(ss01>=hash1) ss01%=hash1;
ss11=ss11*23+str[j]-48;
}
for(int j=1;j+k-1<=len;j++)
{
int h1=(ss01-(ll)ss00*ph1[k]%hash1+hash1)%hash1;
int h2=ss11-ss10*ph2[k];
q[i].push_back((node){h1,h2});
ins(h1,h2);
ss00=ss00*11+str[j]-48; if(ss00>=hash1) ss00%=hash1;
ss01=ss01*11+str[j+k]-48; if(ss01>=hash1) ss01%=hash1;
ss10=ss10*23+str[j]-48;
ss11=ss11*23+str[j+k]-48;
}
}
}
for(int i=1;i<=n;i++) upd(a[i].x,a[i].x,1);
for(int i=1;i<=m;i++)
{
if(e[i][0]==1) link(e[i][1],e[i][2]);
else if(e[i][0]==2) cut(e[i][1]);
else
{
int re=1;
for(int j=0;j<q[i].size();j++)
re=(ll)re*fi(q[i][j].h1,q[i][j].h2)%Mod;
output(re);
}
}
return 0;
}
T3
令f[i][j]表示寬為j的矩形,下i行保證合法,i+1行存在不合法,這個矩形合法的概率
有f[i][j]=sigma p*f[>i][u]*f[>=i][j-u-1] (列舉不合法的位置,p是前i行合法i+1行不合法的概率)
當i>0時,因為i*j<=K,所以狀態數是K的,暴力轉移是K^2的,可以通過
當i=0時,設計另一個dp,g[i]表示前i個格子的答案,列舉連續的一段f[1]更新,因為中間要用不合法分開不是很方便轉移,不妨令N=n+1,每次在末尾放一個不合法和一段f[1],f[N]/(不合法的概率)即答案
令p為一個格子合法的概率
此時g[i]=sigma g[i-j-1]*(1-p) *f[>=1][j], (j=0 to k)
化成一個常係數齊次遞推式: g[i]=g[i-j]*a[j] (j=1 to k+1)
k<=100時可以直接矩乘
對於更大的資料,考慮優化這個東西
然後叉姐寫過一個東西 《矩陣乘法遞推的優化》 (好像是叫這個
然後學過線性代數的同學還是比較容易看懂的? (看不懂怎麼辦我怎麼知道我也沒看懂啊
弄出這個轉移矩陣的特徵多項式,快速冪求 x^n mod 這個特徵多項式 就可以得到答案了
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define __ %=Mod
using namespace std;
const ll Mod = 998244353;
const int maxn = 2100;
ll pw(ll x,int k)
{
ll re=1ll;
for(;k;k>>=1,x=x*x%Mod) if(k&1)
re=re*x%Mod;
return re;
}
ll inv(ll x){return pw(x,Mod-2);}
ll temp[maxn];
void sqr(ll f[],int &len)
{
int ren=len*2;
for(int i=0;i<=len;i++) temp[i]=f[i];
for(int i=0;i<=ren;i++) f[i]=0;
for(int i=0;i<=len;i++)
{
for(int j=0;j<=len;j++)
(f[i+j]+=temp[i]*temp[j]%Mod)__;
}
len=ren;
}
void mod(ll A[],int &an,ll B[],int bn)
{
while(an>=bn)
{
int dec=an-bn;
ll div=A[an]*inv(B[bn]);
for(int j=0;j<=bn;j++) (A[j+dec]-=B[j]*div%Mod)__;
while(!A[an]&&an) an--;
}
}
int n,K; ll p,pi[maxn];
ll f[maxn][maxn],g[maxn][maxn],h[maxn];
ll a[maxn]; int an;
ll s[maxn]; int sn;
int t[maxn],tp;
ll cal()
{
if(!K) return (pw(1-p,n)*inv(1-p)%Mod+Mod)%Mod;
memset(f,0,sizeof f);
memset(g,0,sizeof g);
pi[0]=1ll; for(int i=1;i<=K;i++) pi[i]=pi[i-1]*p%Mod;
for(int i=K;i>=1;i--)
{
for(int j=1;i*j<=K;j++)
{
ll pp=(pi[i]*(1ll-p)%Mod+Mod)%Mod;
for(int k=1;k<=j;k++)
{
ll t1=(k-1)?g[i+1][k-1]:1;
ll t2=(k<j)?g[i][j-k]:1;
(f[i][j]+=t1*t2%Mod*pp%Mod)__;
}
g[i][j]=(g[i+1][j]+f[i][j])%Mod;
}
}
g[1][0]=1ll;
//memset(h,0,sizeof h);
h[0]=1;
for(int i=K;i>=0;i--) g[1][i+1]=g[1][i]*(1ll-p)%Mod;
for(int i=1;i<=K+1;i++)
{
h[i]=0;
for(int j=0;j<i;j++) if(i-j<=K+1) (h[i]+=h[j]*g[1][i-j]%Mod)__;
}
//h[i]=h[i-j-1]*(1-p)*g[1][j] j=0 to k
//h[k+1]=sigma (1-p)*h[i]*g[1][k-i]
//x^k+1-sigma (1-p)*g[1][k-i]*x^i=0;
an=K+2; a[K+2]=1ll;
for(int i=1;i<=K+1;i++) a[i]=-g[1][K+2-i];
memset(s,0,sizeof s);
sn=1; s[1]=1;
tp=0; for(int now=n;now;t[tp++]=now&1,now>>=1);
for(int i=tp-2;i>=0;i--)
{
sqr(s,sn);
if(t[i])
{
sn++;
for(int i=sn;i>=1;i--) s[i]=s[i-1];
s[0]=0;
}
mod(s,sn,a,an);
}
ll re=0ll;
for(int i=0;i<=sn;i++) (re+=s[i]*h[i]%Mod)__;
return (re*inv(1-p)%Mod+Mod)%Mod;
}
int main()
{
int x,y; scanf("%d%d%d%d",&n,&K,&x,&y); ++n;
p=(ll)x*inv(y)%Mod;
ll ans1=cal(); K--;
ll ans2=cal();
printf("%lld\n",(ans1-ans2+Mod)%Mod);
return 0;
}
D2
T1
不考慮有三種選擇的d個地圖,就是裸的2SAT,因為d很小,這d個點其實只有不選A,不選B兩種選擇(不需要再列舉不選C因為前兩種可以覆蓋所有合法情況)
可以2^d列舉d個地圖不選哪個圖,然後跑tarjan的2SAT
跑滿了會T,所以還要加玄學剪枝和隨機優化….
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define id(x,y) x*3-3+y
using namespace std;
inline void read(int &x)
{
char c; while(!((c=getchar())>='0'&&c<='9'));
x=c-'0';
while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
inline void down(int &x,const int &y){if(x>y)x=y;}
const int maxn = 310000;
const int maxm = 210000;
int n,m,d;
int pos[maxn],pn;
struct edge{int y,nex;}a[maxm]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}
int dfn[maxn],low[maxn],id;
int t[maxn],tp; bool insta[maxn];
int bel[maxn],cnt;
void tarjan(const int x)
{
dfn[x]=low[x]=++id; insta[t[++tp]=x]=true;
for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y)
{
if(!dfn[y]) tarjan(y),down(low[x],low[y]);
else if(insta[y]) down(low[x],dfn[y]);
}
if(low[x]==dfn[x])
{
++cnt;
int la=-1;
while(la!=x)
{
insta[la=t[tp--]]=false;
bel[la]=cnt;
}
}
}
char str[maxn];
int e[maxm][4],oth[maxn];
bool v[maxn];
int chosen[maxn],times;
bool judge()
{
times++;
len=0; for(int i=0;i<3*n;i++) fir[i]=0;
for(int i=1;i<=m;i++)
{
int x=e[i][0],y=e[i][2];
if(v[id(x,e[i][1])])
{
if(!v[id(y,e[i][3])]) { x=id(x,e[i][1]); ins(x,oth[x]); continue; }
x=id(x,e[i][1]),y=id(y,e[i][3]);
ins(x,y);
ins(oth[y],oth[x]);
}
}
id=cnt=0; for(int i=0;i<3*n;i++) dfn[i]=low[i]=bel[i]=0,insta[i]=false;
tp=0; for(int i=0;i<3*n;i++) if(v[i]&&!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
{
int x=v[id(i,0)]?id(i,0):id(i,1);
int y=oth[x];
if(bel[x]==bel[y]) return false;
chosen[i]=bel[x]<bel[y]?0:1;
}
return true;
}
bool solve(const int now)
{
if(now>d)
{
if(judge()) return true;
return false;
}
int k=pos[now];
if(times>180) return false;
if(rand()&1)
{
v[id(k,0)]=v[id(k,1)]=true; oth[id(k,0)]=id(k,1); oth[id(k,1)]=id(k,0);
v[id(k,2)]=false;
if(solve(now+1)) return true;
v[id(k,0)]=v[id(k,2)]=true; oth[id(k,0)]=id(k,2); oth[id(k,2)]=id(k,0);
v[id(k,1)]=false;
return solve(now+1);
}
else
{
v[id(k,0)]=v[id(k,2)]=true; oth[id(k,0)]=id(k,2); oth[id(k,2)]=id(k,0);
v[id(k,1)]=false;
if(solve(now+1)) return true;
v[id(k,0)]=v[id(k,1)]=true; oth[id(k,0)]=id(k,1); oth[id(k,1)]=id(k,0);
v[id(k,2)]=false;
return solve(now+1);
}
}
char ss;
int main()
{
srand(333333);
read(n); read(d);
scanf("%s",str);
memset(v,true,sizeof v);
for(int i=1;i<=n;i++)
{
if(str[i-1]=='x') pos[++pn]=i;
else
{
int x,y;
if(str[i-1]=='a') x=id(i,1),y=id(i,2),v[id(i,0)]=false;
else if(str[i-1]=='b') x=id(i,0),y=id(i,2),v[id(i,1)]=false;
else x=id(i,0),y=id(i,1),v[id(i,2)]=false;
oth[x]=y,oth[y]=x;
}
}
read(m);
for(int i=1;i<=m;i++)
{
read(e[i][0]); ss=getchar(); e[i][1]=ss-'A';
read(e[i][2]); ss=getchar(); e[i][3]=ss-'A';
}
if(!solve(1)) return puts("-1"),0;
for(int i=1;i<=n;i++)
{
int x=v[id(i,0)]?id(i,0):id(i,1),d=id(i,0);
printf("%c",'A'+(chosen[i]==0?x-d:oth[x]-d));
}
return 0;
}
T2
有一個不難發現的性質:i天的最優答案選的蔬菜集合一定是i+1天選的蔬菜集合的子集
正解的實質就是模擬費用流過程並用資料結構優化
這題判一種選法是否合法,就是對於任意的t,保質期<=t的蔬菜數<=m*t,用f[n]表示保質期在<=n的蔬菜數,g[n]=f[n]-n*m,當 max g[n] <=0時合法即所有蔬菜都能被賣出
從第1天遞推到第n天,維護一個每種蔬菜的大根堆,貪心的從堆頂依次取m個出來,每種取出其剩餘保質期最大的,線段樹對 [ 保質期,inf ]+1 ,判序列是否合法,不合法就撤銷操作,否則選上這個蔬菜
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn = 210000;
int n,m,u,K;
int a[maxn],s[maxn],ci[maxn],xi[maxn];
struct segment
{
int flag,mx;
}seg[maxn<<2];
void pushdown(const int x)
{
int lc=x<<1,rc=lc|1;
if(seg[x].flag)
{
int fl=seg[x].flag; seg[x].flag=0;
seg[lc].flag+=fl; seg[lc].mx+=fl;
seg[rc].flag+=fl; seg[rc].mx+=fl;
}
}
void pushup(const int x) { seg[x].mx=max(seg[x<<1].mx,seg[x<<1|1].mx); }
void build(const int x,const int l,const int r)
{
if(l==r) { seg[x].mx=-l*m; return; }
int mid=l+r>>1,lc=x<<1,rc=lc|1;
build(lc,l,mid); build(rc,mid+1,r);
pushup(x);
}
int lx,rx,cc;
void upd(const int x,const int l,const int r)
{
if(rx<l||r<lx) return;
if(lx<=l&&r<=rx) { seg[x].flag+=cc; seg[x].mx+=cc; return; }
int mid=l+r>>1,lc=x<<1,rc=lc|1;
pushdown(x);
upd(lc,l,mid); upd(rc,mid+1,r);
pushup(x);
}
int query(const int x,const int l,const int r)
{
if(rx<l||r<lx) return -1;
if(lx<=l&&r<=rx) return seg[x].flag;
pushdown(x);
int mid=l+r>>1,lc=x<<1,rc=lc|1;
return max(query(lc,l,mid),query(rc,mid+1,r));
}
struct node{int x,i,ev;};
inline bool operator <(const node x,const node y){return x.x<y.x;}
priority_queue<node>q;
bool judge(const node x)
{
int oth=ci[x.i]-x.ev;
int ii=xi[x.i]==0?u:(oth/xi[x.i]+(oth%xi[x.i]?1:0));
lx=ii,rx=u; cc=1; upd(1,1,u);
if(seg[1].mx>0) { cc=-1; upd(1,1,u); return false; }
return true;
}
ll ans[maxn];
int main()
{
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=n;i++) scanf("%d%d%d%d",&a[i],&s[i],&ci[i],&xi[i]);
u=100000; build(1,1,u);
for(int i=1;i<=n;i++) q.push((node){a[i]+s[i],i,0});
ll now=0;
for(int i=1;i<=u;i++)
{
int j=m;
while(j&&!q.empty())
{
node x=q.top(); q.pop();
if(judge(x))
{
j--; now+=x.x;
x.ev++; x.x=a[x.i];
if(x.ev!=ci[x.i]) q.push(x);
}
}
ans[i]=now;
if(q.empty())
{
for(int k=i+1;k<=u;k++) ans[k]=now;
break;
}
}
m=K;
while(m--)
{
int x; scanf("%d",&x);
printf("%lld\n",ans[x]);
}
return 0;
}
T3
聽說把動態凸包的模板弄上去就能get AC
樂滋滋說不可做
uoj好像只有std過了qwq
那我還做這個幹嘛