初一下 合集
P6569 【[NOI Online #3 提高组]魔法值(民间数据)】
不知为何,普通的bitset+ksm 竟然WA了,结果一开O2,过了???
我们考虑每个点要满足什么样的条件才能对点1产生贡献。显然,做过CSP-J T4的应该知道:当且仅当这个点走k步,有奇数条到达1的路径。如果直接用矩阵乘法将其算出来,那就是N^3 log n的。
接下来考虑如何优化:我们只关心奇偶性,所以ans[x][y]%2=1当且仅当(Σans[x][k]*ans[k][y])%2=1 对于里面那一坨,我们知道偶数加进去不影响奇偶性,所以ans[x][y]%21当且仅当有奇数个k,满足a[x][k]1&&ans[k][y]==1,因为是无向图,所以ans关于对角线对称,即ans[k][y]=ans[y][k],然后就可以完美地用bitset水过此题啦!
完结撒花O(∩_∩)O~~
#include<bits/stdc++.h> using namespace std; int n,m,q; const int N=105; typedef long long ll; ll f[N]; struct mt{ bitset<N> c[N]; mt(){ for(int i=1;i<=N;++i)c[i].reset(); } }a; inline mt mul(mt x,mt y){ int i,j,k; mt z; for(i=1;i<=n;++i){ for(j=1;j<=n;++j){ z.c[i][j]=((x.c[i]&y.c[j]).count()&1); } } return z; } inline mt qp(mt x,ll y){ mt res=x;y--; while(y){ if(y&1)res=mul(res,x); x=mul(x,x); y>>=1; } return res; } int main(){ //freopen("magic.in","r",stdin); //freopen("magic.out","w",stdout); register int i,j; scanf("%d%d%d",&n,&m,&q); for(i=1;i<=n;++i)scanf("%lld",&f[i]); for(i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); a.c[x][y]=1,a.c[y][x]=1; } while(q--){ ll w; scanf("%lld",&w); mt p=qp(a,w); ll ans=0; for(i=1;i<=n;++i)ans^=f[i]*1ll*p.c[i][1]; printf("%lld\n",ans); } return 0; }
NOIOL3TG划水记
2打得很自闭,这次准备雪耻
T1 水 20mins 切+对拍
T2 直接bitset莽了,没去想换序+倍增 100mins 切+对拍
T3 10mins 10分
T2sb了没开longlong,时隔半年,又见祖宗,以后绝对不能再这样了!
T2正解(未完)
#include<bits/stdc++.h> using namespace std; int n,m,q; const int N=105; typedef long long ll; ll f[N]; struct mt{ ll c[N][N]; mt(){ memset(c,0,sizeof(c)); } }a,t[40]; struct cmt{ ll c[N]; cmt(){ memset(c,0,sizeof(c)); } }b; inline mt mul(cmt x,mt y){ int i,j,k; mt z; for(i=1;i<=n;++i){ for(j=1;j<=n;++j) z.c[i]+=x.c[j]*y.c[i][j]; } return z; } inline mt mu1(mt x,mt y){ int i,j,k; mt z; for(i=1;i<=n;++i){ for(j=1;j<=n;++j) for(k=1;k<=n;++k) z.c[i][j]^=x.c[i][k]&y.c[k][j]; } return z; } int main(){ //freopen("magic.in","r",stdin); //freopen("magic.out","w",stdout); register int i,j; scanf("%d%d%d",&n,&m,&q); for(i=1;i<=n;++i)scanf("%lld",&f[i]); for(i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); a.c[x][y]=1,a.c[y][x]=1; } for(i=1;i<=32;i++)t[i]=mul1(t[i-1],t[i-1]); while(q--){ ll w; scanf("%lld",&w); cmt ans; for(i=0;i<=32;i++){ if((w>>i)&1)ans=mul(ans,t[i]); } printf("%lld\n",ans.c[1]); } return 0; }
P6568 【[NOI Online #3 提高组]水壶】
很显然 将a倒给b和将b倒给a是等价的,所以在[l,r]内,假定是一直从左往右倒,倒k次,最后一桶的水量就是sum[l,r]
所以就很自然地想到最大子段和了,可以用dp或者前缀和解决!
code:
#include<bits/stdc++.h> using namespace std; int n,m; const int N=1000005; typedef long long ll; ll a[N]; int main(){ int i,j; cin>>n>>m; for(i=1;i<=n;i++){ scanf("%lld",&a[i]); a[i]=a[i-1]+a[i]; } ll ans=0; for(i=m+1;i<=n;i++)ans=max(ans,a[i]-a[i-m-1]); printf("%lld\n",ans); return 0; }
CDQ分治学习笔记
一个月没更博客了,更一下。
最近看了一下cdq的论文,感觉这真是个有用的算法。
求三维偏序时,先将所有东西以x为第一关键字,y为第二关键字,z为第三关键字排序,方便后续处理。
solve(l,r):先solve(l,mid),slove(mid+1,r)求出这两段自己内部的答案。然后就是(l,mid)对(mid+1,r)的贡献了(显然,如果所有值都不完全相同的话,只存在(l,mid)对(mid+1,r)的贡献,因为后者对前者有贡献当且仅当ai=aj后bi=bj,ci=cj因为前面有过按关键字排序)
然后分别对这两段区间按y排序(因为左区间中所有x小于等于右区间中所有x),然后以右区间中的每个数,将能对他产生贡献的新加进来,最后计算它的新增答案。
P6514:
#include<bits/stdc++.h> using namespace std; int n,q; const int N=100005; struct node{ int k,ty,x,y; }a[N]; struct BIT{ int c[N]; BIT(){ memset(c,0,sizeof(c)); } int lowbit(int x){ return x&-x; } void add(int x,int v){ while(x<=n){ c[x]+=v; x+=lowbit(x); } } int sum(int x){ int s=0; while(x){ s+=c[x]; x-=lowbit(x); } return s; } }t; int ans[N],vis[N]; int cmp(node u,node v){ return u.k<v.k; } int cmp1(node u,node v){ return u.x<v.x; } void solve(int l,int r){ if(l==r)return ; int i,mid=l+r>>1; solve(l,mid),solve(mid+1,r); sort(a+l,a+mid+1,cmp1),sort(a+mid+1,a+r+1,cmp1); int j=l; for(i=mid+1;i<=r;i++){ while(a[j].x<=a[i].x&&j<=mid){ if(a[j].ty==1)t.add(a[j].y,1); j++; } ans[a[i].k]+=t.sum(n)-t.sum(a[i].y-1); } for(i=l;i<j;i++)if(a[i].ty==1)t.add(a[i].y,-1); } int main(){ int i; cin>>n>>q; for(i=1;i<=q;i++)scanf("%d%d%d",&a[i].ty,&a[i].x,&a[i].y),a[i].k=i,vis[i]=(a[i].ty==2); sort(a+1,a+q+1,cmp); solve(1,q); for(i=1;i<=q;i++)if(vis[i])printf("%d\n",ans[i]); return 0; }
NOI Online#1 划水记
最近兴致大发想丰富一下Blog
说实话,一直到考试前一天我还在颓废,而且至今也不知道这个比赛有什么用。。。我无非就是想一雪前耻,让那些因为CSP的爆炸而说我菜的人闭嘴!(然而打脸了。。。)
提前5分钟进考试,发现这真tm卡,然后看了珞咕上的题面。发现T1完全不可做啊!!!算了瞎写一通:
#include<bits/stdc++.h> using namespace std; int T,n,m; const int N=100001; int a[N],b[N],t[N],u[N],v[N],c[N]; typedef long long ll; int main(){ cin>>T; int i,j; while(T--){ scanf("%d%d",&n,&m); ll s1=0,s2=0; for(i=1;i<=n;i++)scanf("%d",&a[i]),s1+=a[i]; for(i=1;i<=n;i++)scanf("%d",&b[i]),s2+=b[i]; int f1=0,f2=0; for(i=1;i<=m;i++){ scanf("%d%d%d",&t[i],&u[i],&v[i]); if(t[i]==1)f1=1; else f2=1; } if(n==2){ int x=a[1]-a[2],y=a[1]+a[2],u=b[1]-b[2],v=b[1]+b[2]; if(!f1){ if(y==v&&(x-u)%2==0)puts("YES"); else puts("NO"); }else if(!f2){ if(x==u&&(y-v)%2==0)puts("YES"); else puts("NO"); }else{ if((x-u)%2==0)puts("YES"); else puts("NO"); } }else if(!f1){ if(s1-s2==0)puts("YES"); else puts("NO"); }else{ if((s1-s2)%2==0)puts("YES"); else puts("NO"); } } return 0; }
考完后我佛了,这tm有80分。
然后开T2,推出结论后脑子一热直接写,直接过拍。此时我真tm想杀了自己,md数组开小了!!!!!!!!100->20
此时时间所剩无几(4 mins),我迅速敲了T3的暴力,一遍过样例,结果最后没看到弹窗,20->10
CXY神仙:100+100+100=300
期望:80+100+20=200
实际:80+20+10=110
全国rk900+ wzbl
怎么我还是那么菜啊,怎么办。。。看来,要卧薪尝胆了!!!
CF961E 【Tufurama】
全真主席树做法,你值得拥有
(震惊!此题使用主席树,仅需59行即可搞定!)
简化题意,我们可以发现题目要求的是Ai≥j且i≤Aj(1<=i,j<=n)的对数,这是一个二维的东西。
那么,我们要写一个又臭又长的线段树套线段树。喂喂喂,别走啊,我刚刚开van笑的呢!众所周知,主席树也可以维护二维信息,我们就可以利用下标的有序性将题目转化成:在i时间将Ai插入主席树中,最后对于每个i求在Ai时间时主席树中比i小的个数。这样就可以完美解决了。
还有一些细节要注意:我们不需要离散化Ai,因为当Ai≥n与Ai==n实质是相同的。当然,如果Ai≥i ,会将自己统计一次,所以在一开始减掉,最后除以2就行了。
code:
#include<bits/stdc++.h> using namespace std; int n; const int N=200005; int cnt,a[N],rt[N]; typedef long long ll; ll ans; struct node{ int ls,rs,s; node(){ ls=0,rs=0,s=0; } }; node t[N*40]; struct fuck_wxw{ int build(int l,int r){ int now=++cnt; if(l<r){ int mid=l+r>>1; t[now].ls=build(l,mid); t[now].rs=build(mid+1,r); } return now; } int update(int k,int l,int r,int v){ int now=++cnt; t[now]=t[k],t[now].s=t[k].s+1; if(l<r){ int mid=l+r>>1; if(v<=mid)t[now].ls=update(t[k].ls,l,mid,v); else t[now].rs=update(t[k].rs,mid+1,r,v); } return now; } int query(int k,int l,int r,int v){ if(l==r)return t[k].s; int mid=l+r>>1; if(v<=mid){ return t[t[k].rs].s+query(t[k].ls,l,mid,v); }else{ return query(t[k].rs,mid+1,r,v); } } }T; int main(){ int i; cin>>n; ll tot=0; for(i=1;i<=n;i++){ scanf("%d",&a[i]); a[i]=min(a[i],n); if(a[i]>=i)ans--; } rt[0]=T.build(1,n); for(i=1;i<=n;i++)rt[i]=T.update(rt[i-1],1,n,a[i]); for(i=1;i<=n;i++)ans+=1ll*T.query(rt[a[i]],1,n,i); cout<<ans/2<<endl; return 0; }
P3478 【[POI2008]STA-Station】
傻逼换根dp题,用不着解释,直接上代码。
#include<bits/stdc++.h> using namespace std; int n; const int N=1000001; int cnt,h[N<<1],nxt[N<<1],to[N<<1]; void add(int x,int y){ cnt++; nxt[cnt]=h[x]; h[x]=cnt; to[cnt]=y; } typedef long long ll; ll f[N],dp[N],tot[N]; void dfs1(int u,int fa){ tot[u]=1,dp[u]=1; for(int i=h[u];i;i=nxt[i]){ int v=to[i]; if(v!=fa){ dfs1(v,u); dp[u]+=dp[v]+tot[v]; tot[u]+=tot[v]; } } } void dfs2(int u,int fa){ for(int i=h[u];i;i=nxt[i]){ int v=to[i]; if(v!=fa){ f[v]=f[u]+n-2ll*tot[v]; dfs2(v,u); } } } int main(){ int i; cin>>n; for(i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); add(x,y),add(y,x); } dfs1(1,0); f[1]=dp[1]; dfs2(1,0); ll mx=0,mxf; for(i=1;i<=n;i++){ if(f[i]>mx){ mx=f[i]; mxf=i; } } cout<<mxf<<endl; return 0; }
P2607 【[ZJOI2008]骑士】
关于这道题目的找环边,我有一种独特的做法:
大部分人都是dfs将环边出来,那我想:dfs有点复杂了,还有可能会爆栈,所以我回忆起了我们求最小生成树的时候用并查集的做法。因为我们只关心环边,所以在插入一条边的时候如果形成了环那它显然就是环边。然后实现的时候就以环边的编号来记录边的端点、边的编号。因为在DP的时候显然不可能dfs到连通块的外面,所以就直接拿两个端点分别做一遍DP就可以了。
一个连通块的答案就是(设环边为{u,v}) max(f1[u][0],f2[v][0]);最后加在一起就好了。。。
又短又清晰的code:
#include<bits/stdc++.h> using namespace std; int n; const int N=1000001; int a[N],cnt=1,h[N<<1],nxt[N<<1],to[N<<1]; void add(int x,int y){ cnt++; nxt[cnt]=h[x]; h[x]=cnt; to[cnt]=y; } int vis[N],t[N<<1],ff[N]; typedef long long ll; ll f[N][2]; int lop[N][3]; void dp(int u,int fa){ f[u][0]=0; f[u][1]=a[u]*1ll; for(int i=h[u];i;i=nxt[i]){ int v=to[i]; if(v!=fa&&!t[i]){ dp(v,u); f[u][0]+=max(f[v][0],f[v][1]); f[u][1]+=f[v][0]; } } } int gf(int x){ if(x==ff[x])return x; return ff[x]=gf(ff[x]); } int tot; int main(){ cin>>n; int i; for(i=1;i<=n;i++)ff[i]=i; for(i=1;i<=n;i++){ int x; scanf("%d%d",&a[i],&x); add(i,x),add(x,i); int fx=gf(i),fy=gf(x); if(fx==fy)lop[++tot][0]=i,lop[tot][1]=x,lop[tot][2]=cnt; else ff[fx]=fy; } ll ans=0; for(i=1;i<=tot;i++){ int r1=lop[i][0],r2=lop[i][1],r3=lop[i][2]; t[r3]=1,t[r3^1]=1;ll mx=0; dp(r1,0);mx=max(mx,f[r1][0]); dp(r2,0);mx=max(mx,f[r2][0]); ans+=mx; } cout<<ans<<endl; return 0; }
P4197 【Peaks】
应CXY大佬的约来做一下这道题,发现其实真的是个水题
因为看到全部操作都是询问,所以我们可以离线:将所有询问的x从小到大排序,然后一通乱搞把所有边权≤x的边加进来,这个用并查集就可以了。
求第k大显然就是权值线段树对吧!在合并并查集的过程中,我们用线段树合并把对应的权值线段树合并起来,就可以查询了!
注意动态开点和离散化,代码如下:
#include<bits/stdc++.h> using namespace std; const int N=2000001; int n,m,Q,a[N],h[N]; int f[N],rt[N],ans[N]; struct edge{ int fr,to,val; }e[N]; struct queries{ int x,val,rk,id; }q[N]; int gf(int x){ if(x==f[x])return x; return f[x]=gf(f[x]); } int cmp1(edge u,edge v){ return u.val<v.val; } int cmp2(queries u,queries v){ return u.val<v.val; } int tot; int sum[N],ls[N],rs[N],rv[N]; struct sgt{ void update(int x){ sum[x]=sum[ls[x]]+sum[rs[x]]; } int add(int &k,int l,int r,int x){ if(!k)k=++tot; if(l==r){ sum[k]++; return k; } int mid=l+r>>1; if(x<=mid)ls[k]=add(ls[k],l,mid,x); else rs[k]=add(rs[k],mid+1,r,x); update(k); return k; } int merge(int l,int r,int x,int y){ if(!x||!y)return x+y; if(l==r){ sum[x]+=sum[y]; return x; } int mid=l+r>>1; ls[x]=merge(l,mid,ls[x],ls[y]); rs[x]=merge(mid+1,r,rs[x],rs[y]); update(x); return x; } int query(int k,int l,int r,int rk){ if(rk>sum[k])return 0; if(l==r){ return rv[l]; } int mid=l+r>>1,ans; if(rk<=sum[rs[k]])ans=query(rs[k],mid+1,r,rk); else ans=query(ls[k],l,mid,rk-sum[rs[k]]); return ans; } }t; void b_merge(int x,int y){ int fx=gf(x),fy=gf(y); if(fx!=fy){ rt[fx]=t.merge(1,n,rt[fx],rt[fy]); f[fy]=fx; } } int main(){ int i; cin>>n>>m>>Q; for(i=1;i<=n;i++)scanf("%d",&a[i]),h[i]=a[i]; sort(a+1,a+n+1); int p=unique(a+1,a+n+1)-a; for(i=1;i<=n;i++){ int w=lower_bound(a+1,a+p+1,h[i])-a; rv[w]=h[i];h[i]=w;f[i]=i; rt[i]=t.add(rt[i],1,n,h[i]); } for(i=1;i<=m;i++)scanf("%d%d%d",&e[i].fr,&e[i].to,&e[i].val); for(i=1;i<=Q;i++)scanf("%d%d%d",&q[i].x,&q[i].val,&q[i].rk),q[i].id=i; sort(e+1,e+m+1,cmp1),sort(q+1,q+Q+1,cmp2); int lst=0; for(i=1;i<=Q;i++){ while(lst<m&&e[lst+1].val<=q[i].val)lst++,b_merge(e[lst].fr,e[lst].to); ans[q[i].id]=t.query(rt[gf(q[i].x)],1,n,q[i].rk); } for(i=1;i<=Q;i++)printf("%d\n",ans[i]==0?-1:ans[i]); return 0; }
P2698 【[USACO12MAR]花盆Flowerpot】
因为答案满足单调性,所以我们可以二分答案(设为x)。
然后转化题意:按x轴排序,如果对于某个点(u,v),(u-x~u,...)的区间内的点的极差>=d,则符合条件。求定长区间极差,即使用两个单调队列,一个单调升,一个单调降,每次将两队头取出判断即可。
清真code:
#include<bits/stdc++.h> using namespace std; const int N=100001; int n,d; int q1[N],q2[N]; struct node{ int x,y; }a[N]; int cmp(node u,node v){ return u.x<v.x; } int check(int x){ int i,j; memset(q1,0,sizeof(q1)); memset(q2,0,sizeof(q2)); int h1=1,t1=0,h2=1,t2=0; for(i=1;i<=n;i++){ while(h1<=t1&&a[q1[h1]].x<a[i].x-x)h1++; while(h2<=t2&&a[q2[h2]].x<a[i].x-x)h2++; while(h1<=t1&&a[i].y<a[q1[t1]].y)t1--; q1[++t1]=i; while(h2<=t2&&a[i].y>a[q2[t2]].y)t2--; q2[++t2]=i; if(a[q2[h2]].y-a[q1[h1]].y>=d)return 1; } return 0; } int main(){ int i; cin>>n>>d; for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y); sort(a+1,a+n+1,cmp); int l=1,r=1e6,ans=-1; while(l<=r){ int mid=l+r>>1; if(check(mid))ans=mid,r=mid-1; else l=mid+1; } cout<<ans<<endl; return 0; }
P5858 【「SWTR-03」Golden Sword】
思路
其实这道题目一眼就能够写出转移方程:
f[p][j]=f[p^1][k]+a[i]*j;(j-1≤k≤min(j+s-1,w))(此处我用了滚动数组)
然后就有35分,开个longlong 85分;
接下来我们来考虑如何优化:
观察到后面一坨都是a[i]*j,可以提出来;
前面就是在f[p^1]中找到一个长度为s+1的区间最小值(边界先别管)。
可以用一个单调队列,倒序插入,第一步把f[p1][w]插入,后面每次都先将f[p1][j-1]加入,然后删除下标>min(j+s-1,w)的点,这样每次对头就是最优值。(其实不会影响到边界,无脑码过去就可以了)
code:
85pts:
#include<bits/stdc++.h> using namespace std; int n,w,s; const int N=5501; typedef long long ll; ll a[N],f[2][N]; int main(){ cin>>n>>w>>s; int i,j,k; for(i=1;i<=n;i++)scanf("%lld",&a[i]); for(i=0;i<=1;i++)for(j=0;j<=w;j++)f[i][j]=-1e15; f[0][0]=0; int p=1; for(i=1;i<=n;i++){ for(j=0;j<=w;j++)f[p][j]=-1e15; for(j=1;j<=min(i,w);j++){ for(k=j-1;k<=min(j+s-1,w);k++) f[p][j]=max(f[p][j],1ll*f[p^1][k]+1ll*a[i]*j); } p^=1; } ll ans=-1e15; for(i=0;i<=w;i++)ans=max(ans,f[n&1][i]); cout<<ans<<endl; return 0; }
100pts:
#include<bits/stdc++.h> using namespace std; const int N=5501; int n,w,s,q[N]; typedef long long ll; ll a[N],f[2][N]; int main(){ cin>>n>>w>>s; int i,j,k; for(i=1;i<=n;i++)scanf("%lld",&a[i]); for(i=0;i<=1;i++)for(j=0;j<=w;j++)f[i][j]=-1e15; f[0][0]=0; int p=1; for(i=1;i<=n;i++){ for(j=0;j<=w;j++)f[p][j]=-1e15; int h=1,t=1; q[h]=w; for(j=w;j>=1;j--){ while(h<=t&&q[h]>min(j+s-1,w))h++; while(h<=t&&f[p^1][q[t]]<f[p^1][j-1])t--; q[++t]=j-1; f[p][j]=f[p^1][q[h]]+1ll*a[i]*j; } p^=1; } ll ans=-1e15; for(i=0;i<=w;i++)ans=max(ans,f[n&1][i]); cout<<ans<<endl; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】