初一下 合集

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 @ 2023-07-14 14:28  Anticipator  阅读(27)  评论(0)    收藏  举报