初一下 合集

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;
}

posted @   Anticipator  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示