SDOI2017 Round1解题报告
来源:互联网 发布:网络投票公司加盟 编辑:程序博客网 时间:2024/06/05 19:00
虽然考的很差,很不想去再面对这套题,但是只有直面失败才能走向成功。从新审视这套题,才发现自己存在的问题和差距。
Day 1
T1
题解
mobius反演。。。
设
根据莫比乌斯反演的公式
所以式子可以变成
但是实际上式子还能进一步的化简,设
设
我们预处理出
有一点需要特别注意:
代码
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define N 1000000#define LL long long #define p 1000000007#define mod 1000000006using namespace std;int pd[N+3],prime[N+3],n,m,T;LL f[N+3],mu[N+3],inv[N+3],cnt[N+3],fi[N+3];LL quickpow(LL num,LL x){ x=(x%mod+mod)%mod; LL base=num%p; LL ans=1; while (x) { if (x&1) ans=ans*base%p; x>>=1; base=base*base%p; } return ans;}void init(){ mu[1]=1; fi[0]=0; fi[1]=1; inv[0]=1; for (int i=2;i<=N;i++) fi[i]=(fi[i-1]+fi[i-2])%p; for (int i=2;i<=N;i++) { if (!pd[i]) { prime[++prime[0]]=i; mu[i]=-1; } for (int j=1;j<=prime[0];j++) { if (i*prime[j]>N) break; pd[i*prime[j]]=1; if (i%prime[j]==0) { mu[i*prime[j]]=0; break; } mu[i*prime[j]]=-mu[i]; } } for (int i=0;i<=N;i++) f[i]=1; for (int i=1;i<=N;i++) { for (int j=i,t=1;j<=N;j+=i,t++) f[j]=f[j]*quickpow(fi[i],mu[t])%p; } for (int i=2;i<=N;i++) f[i]=f[i]*f[i-1]%p; for (int i=1;i<=N;i++) inv[i]=quickpow(f[i],p-2);}int main(){ freopen("product.in","r",stdin); freopen("product.out","w",stdout); scanf("%d",&T); init(); while (T--){ scanf("%d%d",&n,&m); if (n>m) swap(n,m); LL ans=1; for (int i=1,j;i<=n;i=j+1) { j=min(n/(n/i),m/(m/i)); LL t=(LL)(n/i)*(m/i); ans=ans*quickpow(f[j]*inv[i-1],t)%p; } printf("%I64d\n",ans); }}
T2
题解
LCT+线段树
感觉自己对LCT一直有抵触情绪。。。。所以决定好好写这道题的题解。
一条路径的权值为:路径上的颜色种类和。
我们定义f(x),表示x与fa[x]的颜色是否相同,相同为0,不同为1,令 f(1)=1。g(x)表示x到root路径上的f的和。然后考虑怎么维护g(x)。
因为是一颗有根树,所以我们不牵扯到换根操作,最初的时候所以的节点都是指向他的父节点的。(儿子认父亲,父亲不认儿子)。lct维护的splay中的信息,一定是一条重链的信息。对于这条链来说,splay中所有节点的颜色都是相同的。
现在我们要将x到root的路径染成一种新的颜色,利用access操作实现对节点的修改。
access中有一个砍重儿子的过程,对于上图中的三号紫点来说,砍掉了四号紫点(不是单独的一个点,而是四号紫点所在的splay),对于四号紫点所在的splay维护的重链的链顶节点(就是三号紫点真正的儿子)来说,他子树中的所有点g值都会增加1。
现在一号蓝点变成了三号紫点的重儿子,那么对应的一号蓝点所在的splay维护的重链的链顶节点子树中所有点的g值都减少1.
根据access的过程假设当前点是三号紫点,那么他与root在一棵splay中,转根后他就是splay的根,fa[x]就是0,不在需要更改任何g的值。我们发现对于1-3号紫点他们的g值在染色过程中是不发生改变的,所以可以用lct科学的维护。
那么g值得改变都是针对子树的,所以我们搞出dfs序,那么每次修改都是修改的一段区间,就变成了线段树的区间修改。
对于操作3,直接进行区间查询即可。
对于操作2,x->y 的答案就是g(x)+g(y)-2*g(lca(x,y))+1,进行三次单点查询。
代码
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define N 400003using namespace std;int point[N],nxt[N],v[N],tr[N],delta[N],l[N],r[N],deep[N],mi[20];int n,m,k,col[N],f[N][20],fa[N],ch[N][3],tot,sz,pos[N],cur[N];void add(int x,int y){ tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;}void dfs(int rt){ int x=rt; while (true) { if (!deep[x]) { deep[x]=deep[f[x][0]]+1; for (int i=1;i<=17;i++) { if (deep[x]-mi[i]<0) break; f[x][i]=f[f[x][i-1]][i-1]; } l[x]=r[x]=++sz; pos[sz]=x; cur[x]=point[x]; } bool pd=false; for (int i=cur[x];i;i=nxt[i]) { cur[x]=nxt[i]; if (v[i]!=f[x][0]) { f[v[i]][0]=fa[v[i]]=x; x=v[i]; pd=true; break; } } if (!pd) { int t=f[x][0];// cout<<t<<endl; r[t]=max(r[t],r[x]); if (x==rt) break; x=t; } }}int lca(int x,int y){ if (deep[x]<deep[y]) swap(x,y); int k=deep[x]-deep[y]; for (int i=0;i<=17;i++) if ((k>>i)&1) x=f[x][i]; if (x==y) return x; for (int i=17;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0];}void update(int now){ tr[now]=max(tr[now<<1],tr[now<<1|1]);}void pushdown(int now){ if (delta[now]) { tr[now<<1]+=delta[now]; tr[now<<1|1]+=delta[now]; delta[now<<1]+=delta[now]; delta[now<<1|1]+=delta[now]; delta[now]=0; }}void qjchange(int now,int l,int r,int ll,int rr,int val){ if (ll<=l&&r<=rr) { tr[now]+=val; delta[now]+=val; return; } int mid=(l+r)/2; pushdown(now); if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,val); if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,val); update(now);}int find(int now,int l,int r,int x){ if (l==r) return tr[now]; int mid=(l+r)/2; pushdown(now); if (x<=mid) return find(now<<1,l,mid,x); else return find(now<<1|1,mid+1,r,x);}int query(int now,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tr[now]; int mid=(l+r)/2; int ans=0; pushdown(now); if (ll<=mid) ans=max(ans,query(now<<1,l,mid,ll,rr)); if (rr>mid) ans=max(ans,query(now<<1|1,mid+1,r,ll,rr)); return ans;}bool isroot(int x){ return ch[fa[x]][1]!=x&&ch[fa[x]][0]!=x;}int get(int x){ return ch[fa[x]][1]==x;}void rotate(int x){ int y=fa[x]; int z=fa[y]; int which=get(x); if (!isroot(y)) ch[z][ch[z][1]==y]=x; fa[x]=z; fa[y]=x; ch[y][which]=ch[x][which^1]; fa[ch[x][which^1]]=y; ch[x][which^1]=y;}void splay(int x){ int y; while (!isroot(x)){ y=fa[x]; if (!isroot(y)) rotate(get(y)==get(x)?y:x); rotate(x); }}int get_root(int x){ while (ch[x][0]) x=ch[x][0]; return x;}void access(int x){ int t=0; while (x) { col[x]=k; splay(x); int t1=get_root(ch[x][1]); if (t1) qjchange(1,1,n,l[t1],r[t1],1); ch[x][1]=t; int t2=get_root(t); if (t2) qjchange(1,1,n,l[t2],r[t2],-1); t=x; x=fa[x]; }}int main(){ freopen("paint.in","r",stdin); freopen("paint.out","w",stdout); scanf("%d%d",&n,&m); k=n; mi[0]=1; for (int i=1;i<=18;i++) mi[i]=mi[i-1]*2; for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); } dfs(1); for (int i=1;i<=n;i++) qjchange(1,1,n,l[i],r[i],1); for (int i=1;i<=m;i++) { int opt,x,y; scanf("%d%d",&opt,&x); if (opt==1) k++,access(x); if (opt==2) { scanf("%d",&y); int t=lca(x,y); printf("%d\n",find(1,1,n,l[x])+find(1,1,n,l[y])-2*find(1,1,n,l[t])+1); } if (opt==3) printf("%d\n",query(1,1,n,l[x],r[x])); }}
T3
题解
DP+矩阵乘法
至少有一个数是质数的方案数=无限制的方案数-只有质数的方案数
预处理出转移矩阵,直接上矩阵快速幂即可。
代码
#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>#define N 20000003#define M 103#define mod 20170408#define LL long long using namespace std;bool pd[N]; int prime[N],a[M],b[M],n,m,p;struct data{ LL a[M][M];}e,c;void init(){ for (int i=2;i<=m;i++) { if(!pd[i]) prime[++prime[0]]=i; for (int j=1;j<=prime[0];j++) { if (i*prime[j]>m) break; pd[i*prime[j]]=1; if (i%prime[j]==0) break; } } for (int i=1;i<=m;i++) a[i%p]++; for (int i=1;i<=prime[0];i++) b[prime[i]%p]++; for (int i=0;i<p;i++) b[i]=a[i]-b[i]; //for (int i=0;i<p;i++) cout<<a[i]<<" "; cout<<endl;// for (int i=0;i<p;i++) cout<<b[i]<<" "; cout<<endl;}void clear(data &e){ for (int i=0;i<p;i++) for (int j=0;j<p;j++) e.a[i][j]=0;}data mul(data a,data b){ data c; for (int i=0;i<p;i++) for (int j=0;j<p;j++) { c.a[i][j]=0; for (int k=0;k<p;k++) c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod; } return c;}data quickpow(data num,int x){ data base=num; data ans=c; while (x) { if (x&1) ans=mul(ans,base); x>>=1; base=mul(base,base); } return ans;}LL solve(int a[]){ clear(e); for (int i=0;i<p;i++) for (int j=0;j<p;j++) e.a[i][(i+j)%p]+=a[j],e.a[i][(i+j)%p]%=mod; data ans=quickpow(e,n); return ans.a[0][0];}int main(){ freopen("count.in","r",stdin); freopen("count.out","w",stdout); scanf("%d%d%d",&n,&m,&p); init(); for (int i=0;i<p;i++) c.a[i][i]=1; LL t=solve(a)-solve(b); printf("%I64d\n",(t%mod+mod)%mod);}
Day 2
T1
题解
01分数规划+费用流。
看到
先考虑如果每两个人之间只有一个有关的权值该怎么做?那么问题就变成了最大权匹配。这个貌似有一个叫做KM的算法可以快速求解,但是费用流也很好用啊。
二分答案,然后将边权赋值成
据学长说,把double运算变成整数运算会快哦。
代码
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<cmath>#define N 50003#define inf 1000000000#define eps 1e-9using namespace std;int n,m,tot,point[N],nxt[N],v[N],remain[N],last[N],can[N],S,T;double a[203][230],b[203][203],len[N],dis[N],ans;int head,tail,q[N*10];void add(int x,int y,int z,double k){ tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; len[tot]=k; tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; len[tot]=-k;}int addflow(int s,int t){ int now=t; int ans=inf; while (now!=s) { ans=min(ans,remain[last[now]]); now=v[last[now]^1]; } now=t; while (now!=s) { remain[last[now]]-=ans; remain[last[now]^1]+=ans; now=v[last[now]^1]; } return ans;}bool spfa(int s,int t){ for (int i=1;i<=t;i++) dis[i]=-inf,can[i]=0; can[s]=1; dis[s]=0; head=0; tail=0; q[++tail]=s; while (head<tail) { int now=q[++head]; for (int i=point[now];i!=-1;i=nxt[i]) if (dis[v[i]]<dis[now]+len[i]&&remain[i]) { dis[v[i]]=dis[now]+len[i]; last[v[i]]=i; if (!can[v[i]]) { can[v[i]]=1; q[++tail]=v[i]; } } can[now]=0; } if (dis[t]==-inf) return false; int flow=addflow(s,t); ans+=dis[t]*flow; return true;}void solve(int s,int t){ while (spfa(s,t));}bool check(double mid){ tot=-1; memset(point,-1,sizeof(point)); S=1; T=2*n+2; for (int i=1;i<=n;i++) add(S,i+1,1,0); for (int i=1;i<=n;i++) add(i+n+1,T,1,0); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) add(i+1,j+n+1,1,a[i][j]-mid*b[i][j]); //cout<<tot<<endl; ans=0; solve(S,T); return ans>=-eps;}int main(){ freopen("ball.in","r",stdin); freopen("ball.out","w",stdout); scanf("%d",&n); double sum=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%lf",&a[i][j]),sum=max(sum,a[i][j]); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%lf",&b[i][j]); double l=0; double r=sum; double ans=0; while (r-l>=eps) { double mid=(l+r)/2; if (check(mid)) ans=max(ans,mid),l=mid+eps; else r=mid-eps; } printf("%.6lf\n",ans);}
T2
题解
KMP+高斯消元
设N为未结束状态的概率。
假设用两个串TTH和HTT,设第一个获胜的概率是A,第二个人获胜的概率为B
如果在N后面加上TTH,那么有三种可能
NTTH=A+BTH+BH ,是什么意思呢?就是如果在N后面加入TTH,那么第一个人猜的序列出现在了硬币序列中,第一个人获胜,但是N是什么我们不清楚,但是有可能到达第一个T或者第二个T的时候第二个人就获胜了。
所以对于状态NTTH,可以由三个状态得到。
这样我们得到了n个方程,但是有n+1个未知量,因为所有人获胜的总概率是1,那么我们加入这个方程就成功得到了n+1个未知量,n+1个方程。高斯消元直接解就可以了
那么我们怎么得到每个串之间类似A,B的关系呢?发现满足A的前缀是B的后缀,这不就是失配的定义么。所以直接将两个串连起来,用KMP求失配即可。系数就是
代码
#include<iostream>#include<cstring>#include<cstdio>#include<algorithm>#include<cmath>#define N 1003using namespace std;double mi[N],a[N][N],b[N],ans[N];int n,m,t[N],len;char s[N][N],ch[N];void gauss(int n){ for (int i=1;i<=n;i++){ int num=i; for (int j=i+1;j<=n;j++) if (fabs(a[j][i])>fabs(a[num][i])) num=j; if (num!=i) { for (int j=1;j<=n;j++) swap(a[num][j],a[i][j]); swap(b[num],b[i]); } for (int j=i+1;j<=n;j++) if (a[j][i]) { double t=a[j][i]/a[i][i]; for (int k=1;k<=n;k++) a[j][k]-=a[i][k]*t; b[j]-=b[i]*t; } } for (int i=n;i>=1;i--){ ans[i]=b[i]; for (int j=i+1;j<=n;j++) if (a[i][j]) ans[i]-=ans[j]*a[i][j]; ans[i]/=a[i][i]; }}void calc(){ t[1]=0; for (int i=1;i<=len;i++) { int j=t[i]; while (ch[j]!=ch[i]&&j) j=t[j]; t[i+1]=j+1; }}int main(){ freopen("game.in","r",stdin); freopen("game.out","w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%s",s[i]+1); mi[0]=1.0; for (int i=1;i<=m;i++) mi[i]=mi[i-1]*0.5; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { len=0; for (int k=1;k<=m;k++) ch[++len]=s[i][k]; for (int k=1;k<=m;k++) ch[++len]=s[j][k]; calc(); int k=t[len+1]; while (k>1) { a[i][j]+=mi[m-k+1]; k=t[k]; } } double p=1.0/(double)(1<<m); for (int i=1;i<=n;i++) a[i][n+1]=-p; for (int i=1;i<=n;i++) a[n+1][i]=1.0; b[n+1]=1; gauss(n+1); for (int i=1;i<=n;i++) printf("%.7lf\n",ans[i]);}
T3
题解
线段树
首先对a的求解式子进行变形
其实上面式子的计算就变成了好几部分,我们可以用线段树分开维护一下。
sumx 区间[l,r]中所有xi的和
sumy 区间[l,r]中所有yi的和
xy 区间[l,r]中所有xi*yi的和
sq 区间中所有xi^2的和
squ
sum
tagx 针对x的区间增加标记
tagy 针对y的区间增加标记
cx 针对x的区间覆盖标记
cy 针对y的区间覆盖标记
上面式子中的
设
说几个主要的维护过程吧
(1)
对应到线段树中的修改
(2)
对应到线段树中的修改
(3)增加标记与覆盖标记的冲突
如果是覆盖标记遇到加法标记,覆盖标记正常打,增加标记清零
如果是增加标记遇到覆盖标记,就将增量直接加给覆盖标记
写的过程中有一个小细节需要注意,就i*i可能会炸int,注意加强转啊!!!
代码
#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>#define N 200003#define eps 1e-9#define inf 1000000000using namespace std;struct data { double sumx,sumy,xy,sq,sum,squ; double tagx,tagy,cx,cy;}tr[N*4];double xi[N],yi[N];int n,m;data update(data l,data r){ data now; now.tagx=now.tagy=0; now.cx=now.cy=inf; now.sumx=l.sumx+r.sumx; now.sumy=l.sumy+r.sumy; now.xy=l.xy+r.xy; now.sq=l.sq+r.sq; now.squ=l.squ+r.squ; now.sum=l.sum+r.sum; return now;}void build(int now,int l,int r){ if(l==r) { tr[now].sumx=xi[l]; tr[now].sumy=yi[l]; tr[now].xy=xi[l]*yi[l]; tr[now].sq=xi[l]*xi[l]; tr[now].sum=l; tr[now].squ=(double)l*(double)l; return; } int mid=(l+r)/2; build(now<<1,l,mid); build(now<<1|1,mid+1,r); tr[now]=update(tr[now<<1],tr[now<<1|1]);}void calc(int now,int l,int r,double valx,double valy){ double len=(r-l+1); tr[now].tagx+=valx; tr[now].tagy+=valy; tr[now].sq+=len*valx*valx+2.0*valx*tr[now].sumx; tr[now].xy+=valx*tr[now].sumy+valy*tr[now].sumx+valx*valy*len; tr[now].sumx+=valx*len; tr[now].sumy+=valy*len; if (tr[now].cx==inf&&tr[now].cy==inf) return; tr[now].cx+=valx; tr[now].cy+=valy; tr[now].tagx=tr[now].tagy=0;}void cover(int now,int l,int r,double valx,double valy){ double len=(r-l+1); tr[now].cx=valx; tr[now].cy=valy; tr[now].tagx=tr[now].tagy=0; tr[now].sq=tr[now].squ+tr[now].sum*2.0*valx+valx*valx*len; tr[now].xy=tr[now].squ+(valx+valy)*tr[now].sum+valx*valy*len; tr[now].sumx=tr[now].sum+len*valx; tr[now].sumy=tr[now].sum+len*valy;}void pushdown(int now,int l,int r){ int mid=(l+r)/2; if (tr[now].cx!=inf||tr[now].cy!=inf){ cover(now<<1,l,mid,tr[now].cx,tr[now].cy); cover(now<<1|1,mid+1,r,tr[now].cx,tr[now].cy); tr[now].cx=tr[now].cy=inf; } if (fabs(tr[now].tagx)>=eps||fabs(tr[now].tagy)>=eps) { calc(now<<1,l,mid,tr[now].tagx,tr[now].tagy); calc(now<<1|1,mid+1,r,tr[now].tagx,tr[now].tagy); tr[now].tagx=0; tr[now].tagy=0; }}data query(int now,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tr[now]; int mid=(l+r)/2; pushdown(now,l,r); data ans; bool pd=false; if (ll<=mid) ans=query(now<<1,l,mid,ll,rr),pd=true; if (rr>mid) { if (pd) ans=update(ans,query(now<<1|1,mid+1,r,ll,rr)); else ans=query(now<<1|1,mid+1,r,ll,rr); } return ans;}void qjchange(int now,int l,int r,int ll,int rr,double valx,double valy){ if (ll<=l&&r<=rr) { calc(now,l,r,valx,valy); return; } int mid=(l+r)/2; pushdown(now,l,r); if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,valx,valy); if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,valx,valy); tr[now]=update(tr[now<<1],tr[now<<1|1]);}void qjcover(int now,int l,int r,int ll,int rr,double valx,double valy){ if (ll<=l&&r<=rr) { cover(now,l,r,valx,valy); return; } int mid=(l+r)/2; pushdown(now,l,r); if (ll<=mid) qjcover(now<<1,l,mid,ll,rr,valx,valy); if (rr>mid) qjcover(now<<1|1,mid+1,r,ll,rr,valx,valy); tr[now]=update(tr[now<<1],tr[now<<1|1]);}int main(){ freopen("relative.in","r",stdin); freopen("relative.out","w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%lf",&xi[i]); for (int i=1;i<=n;i++) scanf("%lf",&yi[i]); build(1,1,n); for (int i=1;i<=m;i++) { int opt,l,r; scanf("%d",&opt); if (opt==1) { scanf("%d%d",&l,&r); data a=query(1,1,n,l,r); double len=r-l+1; double ans=0,ans1=0; double bx=a.sumx/len; double by=a.sumy/len; ans=a.xy-bx*a.sumy-by*a.sumx+bx*by*len; ans1=len*bx*bx+a.sq-2.0*bx*a.sumx; printf("%.10lf\n",ans/ans1); } if (opt==2){ double s,t; scanf("%d%d%lf%lf",&l,&r,&s,&t); qjchange(1,1,n,l,r,s,t); } if (opt==3) { double s,t; scanf("%d%d%lf%lf",&l,&r,&s,&t); qjcover(1,1,n,l,r,s,t); } }}
- SDOI2017 Round1解题报告
- SDOI2017 Round1 解题报告
- SDOI2017 Round1 解题报告
- SDOI2017 Round1滚粗记
- SDOI2017 Round1 续命记
- COCI2013/2014#ROUND1完整解题报告
- 2016"百度之星" - 资格赛(Astar Round1)【解题报告】
- 2016"百度之星" - 资格赛(Astar Round1)解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- 解题报告
- Antiprime解题报告
- expr解题报告
- 华容道解题报告
- FCKeditor XML request error 404
- 网页上适时显示的时间&倒计时
- NYOJ 803 A/B Problem(java)
- 折叠、展开
- ARToolKit在VS2013下的使用配置步骤
- SDOI2017 Round1解题报告
- unity3d(3)GameObject-游戏对象类
- Chinaz菜单
- 使用FastJson对数据的常用操作
- Spring Boot的新Gradle插件详解
- 无刷新弹出可拖动登录窗口
- 机器学习中的各种距离
- socket基本连接
- 如何在Ubuntu 16.04上安装并配置Redis