暑期集訓第十天(7-1)題解及總結
小總結:
昨天調一道樹剖的題人都弄傻了,所以就沒有寫題解(我們調這一道題的表示都很噁心),今天一起補上吧,還有幾天就要高考了,我們也準備搬著主機挪到國際部了,今天老師們都去調網路了,一下午只有我們在機房裡面頹......
T1:最大公約數和最小公倍數問題
emm...這道題直接寫暴力都能水幾十分,但是虎哥給出的正解比暴力要快一些,就是統計出兩數除最大公因數之外的質數因子,統計好後直接算2的次方即可.
#include<bits/stdc++.h> using namespace std; #define int long long int prime[50050000],vis[5005000],tot,ca[500500],cb[500500]; signed main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int x,y; scanf("%lld%lld",&x,&y); if(x==y){ printf("1\n"); return 0; } for(int i=2;i<=y;++i){ if(!vis[i]){ vis[i]=i; prime[++tot]=i; } for(int j=1;j<=tot&&i*prime[j];++j){ if(prime[j]>vis[i]||prime[j]>y/i) break; vis[i*prime[j]]=prime[j]; } } int flag=0; int s1=0,s2=0; for(int i=1;i<=tot&&prime[i]<=x;++i){ while(x%prime[i]==0){ x/=prime[i]; ++ca[prime[i]]; if(ca[prime[i]]==1) ++s1; } } int cnt=0; for(int i=1;i<=tot&&prime[i]<=y;++i){ while(y%prime[i]==0){ y/=prime[i]; ++cb[prime[i]]; if(cb[prime[i]]==1) ++s2; } if(ca[prime[i]]>0&&ca[prime[i]]<=cb[prime[i]]) { ++flag; if(cb[prime[i]]>ca[prime[i]]) ++cnt; } } if(flag!=s1) printf("0\n"); else{ if(cnt==0) printf("%lld\n",(int)1<<(s2-s1)); else printf("%lld\n",(int)1<<(s2-s1+cnt)); } return 0; }
T2:方格取數
這道題我當時的想法就是先跑一邊二維的dp,記錄路徑後清空,之後再跑一邊,將兩次dp所得的答案進行加和即可.但是這是貪心,很明顯是錯誤的,於是考慮陣列開上4維,分別表示第一次的位置和第二次的位置,轉移的時候判斷兩人是否走到了一起,在一起則加一次得分,否則兩個點位置都加.最後輸出終點得分即可.
#include<bits/stdc++.h> //#define debug printf("-debug-\n") using namespace std; int dp[100][100][100][100],a[500][500]; signed main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int n; scanf("%d",&n); int x,y,z; while(scanf("%d%d%d",&x,&y,&z)==3&&x&&y&&z){ a[x][y]=z; } //debug; for(int i=1;i<=n;++i){ //debug; for(int j=1;j<=n;++j) for(int k=1;k<=n;++k) for(int p=1;p<=n;++p){ //debug; if(i==k && j==p){ dp[i][j][k][p]=max(dp[i][j][k][p],max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1])+a[k][p]); dp[i][j][k][p]=max(dp[i][j][k][p],max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1])+a[k][p]); } else{ dp[i][j][k][p]=max(dp[i][j][k][p],max(dp[i-1][j][k-1][p],dp[i-1][j][k][p-1])+a[i][j]+a[k][p]); dp[i][j][k][p]=max(dp[i][j][k][p],max(dp[i][j-1][k-1][p],dp[i][j-1][k][p-1])+a[i][j]+a[k][p]); } } } printf("%d\n",dp[n][n][n][n]); return 0; }
T3: WYT的刷子
這道題在這次考試之中算是比較難的一道題了(本來做出來了,沒開longlong黑掉了我60pts >_<) ,老師講課的時候用到的是優先佇列,而我對這個東西不怎麼熟悉,所以考試的時候用到的是線段樹,建兩個線段樹,分別維護區間最大值和刷到的最小值,程式碼的核心在這一行:
if(Min1<=pre&&Min1<=Min2&&i<r+1&&j!=n) continue;
這行程式碼用來去除掉不用刷的情況,Min1表示當前刷到的最小值,Min2表示下一位刷到的最小值,pre表示上一次刷得到的最小值,r表示上一次刷到的右邊界,如果當前刷到的高度比前一次和下一次都要低,我們儘可能的不在這裡刷,除了它的左邊界大到再不刷就出現斷層或是到了最友端,最後統計答案即可.
#include<bits/stdc++.h> using namespace std; #define int long long #define lson (t<<1) #define rson (t<<1|1) #define mid ((l+r)>>1) int a[5000500]; int Pow(int x,int y){ int ans=1; while(y){ if(y&1) ans*=x; y>>=1; x=x*x; } return ans; } int tree[6005000],tre[6005000],lazy[6005000]; void pushup(int t){ tree[t]=min(tree[lson],tree[rson]); } void pushdown(int t,int l,int r){ tre[lson]=max(tre[lson],lazy[t]); tre[rson]=max(tre[rson],lazy[t]); lazy[lson]=max(lazy[lson],lazy[t]); lazy[rson]=max(lazy[rson],lazy[t]); } void build(int t,int l,int r){ if(l==r){ tree[t]=a[l]; return; } build(lson,l,mid);build(rson,mid+1,r); pushup(t); } int query(int t,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr){ return tree[t]; } if(qr<=mid) query(lson,l,mid,ql,qr); else if(ql>mid) query(rson,mid+1,r,ql,qr); else{ return min(query(lson,l,mid,ql,qr),query(rson,mid+1,r,ql,qr)); } } void change(int t,int l,int r,int cl,int cr,int num){ if(cl<=l&&r<=cr){ tre[t]=max(tre[t],num); lazy[t]=max(lazy[t],num); //printf("=%d %d %d\n",l,r,tre[t]); return; } pushdown(t,l,r); if(cr<=mid) change(lson,l,mid,cl,cr,num); else if(cl>mid) change(rson,mid+1,r,cl,cr,num); else{ change(lson,l,mid,cl,cr,num);change(rson,mid+1,r,cl,cr,num); } tre[t]=max(tre[lson],tre[rson]); //printf("%d %d %d\n",l,r,tre[t]); } int quer(int t,int l,int r,int q){ if(l==r){ //printf("=%d %d\n",l,tre[t]); return tre[t]; } pushdown(t,l,r); if(q<=mid) return quer(lson,l,mid,q); else return quer(rson,mid+1,r,q); } int b[5000500],pre=0,r,ans1,c[5000500]; signed main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int n,m; scanf("%lld%lld",&n,&m); for(int i=1;i<=n;++i){ scanf("%lld",&a[i]); } build(1,1,n); int cnt=0; r=0; for(int i=1,j;(j=i+m-1)<=n;++i){ int Min1=query(1,1,n,i,j); int Min2=query(1,1,n,i+1,j+1); if(Min1<=pre&&Min1<=Min2&&i<r+1&&j!=n) continue; change(1,1,n,i,j,Min1); pre=Min1;r=j; cnt++; } for(int i=1;i<=n;++i){ ans1+=a[i]-quer(1,1,n,i); //printf("=%d\n",quer(1,1,n,i)); } printf("%lld\n%lld\n",ans1,cnt); return 0; }
T4:2017種樹
這道題的程式碼不怎麼好調(寫第三題的線段樹把時間用光了),這道題首先不能讀錯題目,由於取模的存在,一顆樹可能在他之前的樹的位置之前,我們容易知道一棵樹的花費就是前面點的個數乘上當前的點的座標減去之前的點的座標和(注意絕對值),我們可以維護一顆線段樹,表示在當前位置的樹的個數和座標和,之後拿當前點的座標減去前面點的座標,再拿後面點的座標和減去當前點的座標就可以了.
#include<bits/stdc++.h> using namespace std; #define int long long #define debug printf("-debug-\n") #define lson (t<<1) #define rson (t<<1|1) #define mid ((l+r)>>1) const int M=1000000007; const int N=1e6+10; struct Tree{ int w,cnt; }tree[N]; int n,Mod,x[500500],a,b,ans=1,pre; void pushup(int t){ tree[t].w=(tree[lson].w+tree[rson].w)%M; tree[t].cnt=(tree[lson].cnt+tree[rson].cnt)%M; } void change(int t,int l,int r,int pos){ if(l==r){ tree[t].cnt=(tree[t].cnt+1)%M; tree[t].w=(tree[t].w+pos)%M; return; } if(pos<=mid) change(lson,l,mid,pos); else change(rson,mid+1,r,pos); pushup(t); } int queryw(int t,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr){ return tree[t].w%M; } if(qr<=mid) return queryw(lson,l,mid,ql,qr)%M; else if(ql>mid) return queryw(rson,mid+1,r,ql,qr)%M; else return (queryw(lson,l,mid,ql,qr)+queryw(rson,mid+1,r,ql,qr))%M; } int querycnt(int t,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr){ return tree[t].cnt%M; } if(qr<=mid) return querycnt(lson,l,mid,ql,qr)%M; else if(ql>mid) return querycnt(rson,mid+1,r,ql,qr)%M; else return (querycnt(lson,l,mid,ql,qr)+querycnt(rson,mid+1,r,ql,qr))%M; } signed main(){ scanf("%lld%lld%lld%lld%lld",&n,&Mod,&x[0],&a,&b); x[0]%=Mod; for(int i=1;i<n;++i){ x[i]=(x[i-1]*a+b)%Mod; } change(1,0,Mod-1,x[0]); for(int i=1;i<n;++i){ int a,b,now=0; a=querycnt(1,0,Mod-1,0,x[i]); b=queryw(1,0,Mod-1,0,x[i]); now=(now+a*x[i])%M; now=(now-b)%M; a=querycnt(1,0,Mod-1,x[i],Mod-1); b=queryw(1,0,Mod-1,x[i],Mod-1); now=(now+b)%M; now=(now-a*x[i]+M)%M; ans=ans*now%M; change(1,0,Mod-1,x[i]); } printf("%lld\n",ans); return 0; }
一道莫名噁心難調的樹剖題目:P4315 月下“毛景樹”
題目連結:https://www.luogu.com.cn/problem/P4315
這道題和其他普通樹剖的區別就是這個樹只有邊權而沒有點權,但是我們可以考慮把這個點與其父親結點之間邊的邊權轉化為這個點的點權,之後就可以愉快的樹剖了,但是注意由於不包括父親節點,查詢的時候左端點的編號要加一(除錯真噁心).
#include<bits/stdc++.h> using namespace std; const int maxn=4e6+5; struct asd{ int from,to,next,val; }b[maxn]; int head[maxn],tot=1; void ad(int aa,int bb,int cc){ b[tot].from=aa; b[tot].to=bb; b[tot].val=cc; b[tot].next=head[aa]; head[aa]=tot++; } int dep[maxn],sizz[maxn],son[maxn],f[maxn]; int uu[maxn],vv[maxn],ww[maxn]; void dfs(int now,int fa){ sizz[now]=1; for(int i=head[now];i!=-1;i=b[i].next){ int u=b[i].to; if(u==fa) continue; dep[u]=dep[now]+1; f[u]=now; dfs(u,now); sizz[now]+=sizz[u]; if(son[now]==0 || sizz[son[now]]<sizz[u]){ son[now]=u; } } } int dfn[maxn],rk[maxn],a[maxn],tp[maxn],cnt; void dfs2(int now,int top){ dfn[now]=++cnt; rk[cnt]=a[now]; tp[now]=top; if(son[now]) dfs2(son[now],top); for(int i=head[now];i!=-1;i=b[i].next){ int u=b[i].to; if(u==f[now] || u==son[now]) continue; dfs2(u,u); } } struct trr{ int l,r,siz,laz,w,tag; }tr[maxn]; void push_up(int da){ tr[da].w=max(tr[da<<1].w,tr[da<<1|1].w); } void push_down(int da){ if(tr[da].laz>=0){ tr[da<<1].tag=tr[da<<1|1].tag=0; tr[da<<1].w=tr[da<<1].laz=tr[da<<1|1].w=tr[da<<1|1].laz=tr[da].laz; tr[da].laz=-1; } if(tr[da].tag){ tr[da<<1].tag+=tr[da].tag; tr[da<<1|1].tag+=tr[da].tag; tr[da<<1].w+=tr[da].tag; tr[da<<1|1].w+=tr[da].tag; tr[da].tag=0; } } void build(int da,int l,int r){ tr[da].l=l,tr[da].r=r,tr[da].siz=r-l+1,tr[da].laz=-1; if(l==r){ tr[da].w=rk[l]; return; } int mids=(l+r)>>1; build(da<<1,l,mids); build(da<<1|1,mids+1,r); push_up(da); } void xg(int da,int l,int r,int w){ if(tr[da].l>=l && tr[da].r<=r){ tr[da].w=w; tr[da].tag=0; tr[da].laz=w; return; } push_down(da); int mids=(tr[da].l+tr[da].r)>>1; if(l<=mids) xg(da<<1,l,r,w); if(r>mids) xg(da<<1|1,l,r,w); push_up(da); } void add(int da,int l,int r,int w){ if(tr[da].l>=l && tr[da].r<=r){ tr[da].w+=w; tr[da].tag+=w; return; } push_down(da); int mids=(tr[da].l+tr[da].r)>>1; if(l<=mids) add(da<<1,l,r,w); if(r>mids) add(da<<1|1,l,r,w); push_up(da); } int cx(int da,int l,int r){ if(tr[da].l>=l && tr[da].r<=r){ return tr[da].w; } push_down(da); int mids=(tr[da].l+tr[da].r)>>1; int ans=0; if(l<=mids) ans=max(ans,cx(da<<1,l,r)); if(r>mids) ans=max(ans,cx(da<<1|1,l,r)); return ans; } void trxg(int u,int v,int w){ while(tp[u]!=tp[v]){ if(dep[tp[u]]<dep[tp[v]]) swap(u,v); xg(1,dfn[tp[u]],dfn[u],w); u=f[tp[u]]; } if(dep[u]<dep[v]) swap(u,v); //printf("%d %d\n",dfn[v]+1,dfn[u]); if(dfn[v]<dfn[u]) xg(1,dfn[v]+1,dfn[u],w); } void trad(int u,int v,int w){ while(tp[u]!=tp[v]){ if(dep[tp[u]]<dep[tp[v]]) swap(u,v); add(1,dfn[tp[u]],dfn[u],w); u=f[tp[u]]; } if(dep[u]<dep[v]) swap(u,v); //printf("%d %d\n",dfn[v]+1,dfn[u]); if(dfn[v]<dfn[u]) add(1,dfn[v]+1,dfn[u],w); } int trcx(int u,int v){ int ans=0; while(tp[u]!=tp[v]){ if(dep[tp[u]]<dep[tp[v]]) swap(u,v); //printf("%d %d\n",dfn[tp[u]],dfn[u]); ans=max(ans,cx(1,dfn[tp[u]],dfn[u])); u=f[tp[u]]; } if(dep[u]<dep[v]) swap(u,v); if(dfn[v]<dfn[u]) ans=max(ans,cx(1,dfn[v]+1,dfn[u])); return ans; } int main(){ /*freopen("P4315_1.in","r",stdin); freopen("my.out","w",stdout);*/ memset(head,-1,sizeof(head)); int n; scanf("%d",&n); for(int i=1;i<n;i++){ int aa,bb,cc; scanf("%d%d%d",&aa,&bb,&cc); ad(aa,bb,cc); ad(bb,aa,cc); uu[i]=aa; vv[i]=bb; ww[i]=cc; } dep[1]=1; dfs(1,0); for(int i=1;i<n;i++){ int aa=uu[i],bb=vv[i],cc=ww[i]; if(dep[aa]<dep[bb]) a[bb]=cc; else a[aa]=cc; } dfs2(1,1); build(1,1,n); char s[10]; while(scanf("%s",s)!=EOF && s[0]!='S'){ if(s[0]=='M'){ int aa,bb; scanf("%d%d",&aa,&bb); printf("%d\n",trcx(aa,bb)); } else if(s[0]=='A'){ int aa,bb,cc; scanf("%d%d%d",&aa,&bb,&cc); trad(aa,bb,cc); } else if(s[0]=='C' && s[1]=='o'){ int aa,bb,cc; scanf("%d%d%d",&aa,&bb,&cc); trxg(aa,bb,cc); } else { int aa,bb; scanf("%d%d",&aa,&bb); trxg(uu[aa],vv[aa],bb); } } return 0; }
總結:
其實這篇題解是7月2號補的,昨天調上面那道題調了一晚上......昨天成績有所下滑,已經將近掉出了前十了,說明自己在程式碼準確性和讀題上還存有問題(開longlong),在之後的練習之中還要進行注意,另外今天和liuchang嘗試了進行gdb學習(感覺還不如我在程式碼中輸出中間變數好用),在以後應該可以派上用場,最後,一如既往的,希望明天(7月3日)會更好.