高一上七月下旬日记

7.21

闲话

  • 早上起床铃只放了一声,《欢乐颂》干脆没放。以为体活食堂能早点开门,遂睡到 6:25 就起来去食堂了,结果 6:40 才开门。到机房刷脸打卡的时候是 7:00:43 左右。
  • 上午 7:3011:30 @Delov 学长安排了一场模拟赛。
  • 模拟赛打到一半被 field 叫回宿舍整改内务,说 huge 已经在宿舍等我们了。“放风”一半发现 feifei 也从后面追来了,遂跑着回到宿舍整改内务。走的时候 huge 说以后集训就按今天的标准,下周领导视察很多,别出问题。
  • 午休 huge 查宿。
  • 下午 huge 来说了今年国赛的情况,说了下 NOI 向类 IOI 赛制转变对 HZOI 的影响;举了 @APJifengc 的例子; D 了下他们模拟赛遇到比较难写的题选择口胡而不写代码,说早期几届学长经常写一些码量比较大的题,但现在选择写这种题的人越来越少了;现在学长挨个翻代码指出我们的问题就要虚心改正,这都是学长之前吃过的亏或遇到的问题。
  • 晚上 18:4520:45 @Chen_jr 学长安排了一场模拟赛。
  • 晚休 field 查宿。

做题纪要

CF685B Kay and Snowflake

  • 对于每组询问遍历一遍整棵子树不可接受。

  • 接着考虑重心的一些性质。

    • x 为根的子树的重心一定在以 x 的所有子节点为根的子树的重心到 x 的路径上。
  • 求出重心后暴力向上跳即可。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[600010];
    int head[600010],siz[600010],weight[600010],ans[600010],fa[600010],cnt=0;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(int x,int father)
    {
    	siz[x]=1;
    	fa[x]=father;
    	weight[x]=0;
    	ans[x]=x;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dfs(e[i].to,x);
    			siz[x]+=siz[e[i].to];
    			weight[x]=max(weight[x],siz[e[i].to]);
    		}
    	}
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		int y=ans[e[i].to];
    		while(y!=x)
    		{
    			if(max(weight[y],siz[x]-siz[y])<=siz[x]/2)
    			{
    				ans[x]=y;
    				break;
    			}
    			else
    			{
    				y=fa[y];
    			}
    		}
    	}
    }
    int main()
    {
    	int n,m,u,v,i;
    	cin>>n>>m;
    	for(i=2;i<=n;i++)
    	{
    		cin>>u;
    		v=i;
    		add(u,v);
    	}
    	dfs(1,0);
    	for(i=1;i<=m;i++)
    	{
    		cin>>u;
    		cout<<ans[u]<<endl;
    	}
    	return 0;
    }
    

CF958C3 Encryption (hard)

[ARC148C] Lights Out on Tree

[ARC153C] ± Increasing Sequence

luogu P2056 [ZJOI2007] 捉迷藏

7.22

闲话

做题纪要

CF958C2 Encryption (medium)

  • 多倍经验: CF958C1 Encryption (easy)

  • CF958C3 Encryption (hard) ,取使 fh,j1 取到最大值的 h 即可。

    点击查看代码
    ll a[500010],sum[500010],f[500010][2];
    int main()
    {
    	ll n,k,p,maxx,pos,i,j;
    	scanf("%lld%lld%lld",&n,&k,&p);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    		sum[i]=sum[i-1]+a[i];
    	}
    	f[0][0]=0;
    	for(j=1;j<=k;j++)
    	{
    		maxx=f[j-1][(j-1)&1];
    		pos=j-1; 
    		for(i=j;i<=n;i++)
    		{
    			f[i][j&1]=f[pos][(j-1)&1]+(sum[i]-sum[pos])%p;
    			if(f[i][(j-1)&1]>maxx)
    			{
    				maxx=f[i][(j-1)&1];
    				pos=i;
    			}
    		}
    	} 
    	printf("%lld\n",f[n][k&1]);
    	return 0;
    }
    

P145 修仙(restart)

luogu P2617 Dynamic Rankings

  • 树状数组套主席树板子。
    • 每次修改都会对后续操作产生影响,对主席树进行暴力修改时空复杂度均不能接受。考虑树状数组来维护变化的过程。
    • 树状数组的每一个节点都是一颗主席树(叫做动态开点权值线段树可能更合适)。
    • 操作中的修改就能转化成单点修改,查询时将设计到的树状数组的节点提出来进行询问。
      • 若将原树的点也看做修改操作,时空复杂度为 O((n+m)log2n)

        点击查看代码
        struct ask
        {
        	int l,r,k;
        	char pd;
        }q[200010];
        int a[200010],b[200010];
        struct PDS_SMT
        {
        	int root[200010],rt_sum=0;
        	struct SegmentTrew
        	{
        		int ls,rs,sum;
        	}tree[200010<<8];
        	#define lson(rt) tree[rt].ls
        	#define rson(rt) tree[rt].rs
        	int build_rt()
        	{
        		rt_sum++;
        		return rt_sum;
        	}
        	void build_tree(int &rt,int l,int r)
        	{
        		rt=build_rt();
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		build_tree(lson(rt),l,mid);
        		build_tree(rson(rt),mid+1,r);
        	}
        	void update(int &rt,int l,int r,int pos,int val)
        	{
        		rt=(rt==0)?build_rt():rt;
        		tree[rt].sum+=val;
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		if(pos<=mid)
        		{
        			update(lson(rt),l,mid,pos,val);
        		}
        		else
        		{
        			update(rson(rt),mid+1,r,pos,val);
        		}
        	}
        	int query(int rt1[],int len1,int rt2[],int len2,int l,int r,int k)
        	{
        		if(l==r)
        		{
        			return l;
        		}
        		int mid=(l+r)/2,sum=0;
        		for(int i=1;i<=len2;i++)
        		{
        			sum+=tree[lson(rt2[i])].sum;
        		}
        		for(int i=1;i<=len1;i++)
        		{
        			sum-=tree[lson(rt1[i])].sum;
        		}
        		if(k<=sum)
        		{
        			for(int i=1;i<=len2;i++)
        			{
        				rt2[i]=lson(rt2[i]);
        			}
        			for(int i=1;i<=len1;i++)
        			{
        				rt1[i]=lson(rt1[i]);
        			}   
        			return query(rt1,len1,rt2,len2,l,mid,k);
        		}
        		else
        		{
        			for(int i=1;i<=len2;i++)
        			{
        				rt2[i]=rson(rt2[i]);
        			}
        			for(int i=1;i<=len1;i++)
        			{
        				rt1[i]=rson(rt1[i]);
        			}   
        			return query(rt1,len1,rt2,len2,mid+1,r,k-sum);
        		}
        	}
        }T;
        struct BIT
        {
        	int rt[2][32];
        	int lowbit(int x)
        	{
        		return (x&(-x));
        	}
        	void add(int n,int x,int val)
        	{
        		int pos=lower_bound(b+1,b+1+b[0],a[x])-b;
        		for(int i=x;i<=n;i+=lowbit(i))
        		{
        			T.update(T.root[i],1,b[0],pos,val);
        		}
        	}
        	int query(int l,int r,int k)
        	{
        		memset(rt,0,sizeof(rt));
        		for(int i=r;i>=1;i-=lowbit(i))
        		{
        			rt[1][0]++;
        			rt[1][rt[1][0]]=T.root[i];
        		}
        		for(int i=l-1;i>=1;i-=lowbit(i))
        		{
        			rt[0][0]++;
        			rt[0][rt[0][0]]=T.root[i];
        		}
        		return T.query(rt[0],rt[0][0],rt[1],rt[1][0],1,b[0],k);
        	}
        }B;
        int main()
        {
        	int n,m,i;
        	cin>>n>>m;
        	for(i=1;i<=n;i++)
        	{
        		cin>>a[i];
        		b[0]++;
        		b[b[0]]=a[i];
        	}
        	for(i=1;i<=m;i++)
        	{
        		cin>>q[i].pd>>q[i].l>>q[i].r;
        		if(q[i].pd=='Q')
        		{
        			cin>>q[i].k;
        		}
        		else
        		{
        			b[0]++;
        			b[b[0]]=q[i].r;
        		}
        	}
        	sort(b+1,b+1+b[0]);
        	b[0]=unique(b+1,b+1+b[0])-(b+1);
        	for(i=1;i<=n;i++)
        	{
        		B.add(n,i,1);
        	}
        	for(i=1;i<=m;i++)
        	{
        		if(q[i].pd=='Q')
        		{
        			cout<<b[B.query(q[i].l,q[i].r,q[i].k)]<<endl;
        		}
        		else
        		{
        			B.add(n,q[i].l,-1);
        			a[q[i].l]=q[i].r;
        			B.add(n,q[i].l,1);
        		}
        	}
        	return 0;
        }
        
      • 不将原树的点看做修改操作,开始时建立一棵静态主席树,查询时把主席树也提出来进行询问,时空复杂度为 O(nlogn+mlog2n)

        点击查看代码
        struct ask
        {
        	int l,r,k;
        	char pd;
        }q[200010];
        int a[200010],b[200010];
        struct PDS_SMT
        {
        	int root1[200010],root2[200010],rt_sum=0;
        	struct SegmentTrew
        	{
        		int ls,rs,sum;
        	}tree[200010<<5];
        	#define lson(rt) tree[rt].ls
        	#define rson(rt) tree[rt].rs
        	int build_rt()
        	{
        		rt_sum++;
        		return rt_sum;
        	}
        	void build_tree(int &rt,int l,int r)
        	{
        		rt=build_rt();
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		build_tree(lson(rt),l,mid);
        		build_tree(rson(rt),mid+1,r);
        	}
        	void update1(int pre,int &rt,int l,int r,int pos)
        	{
        		rt=build_rt();
        		tree[rt]=tree[pre];
        		tree[rt].sum++;
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		if(pos<=mid)
        		{
        			update1(lson(pre),lson(rt),l,mid,pos);
        		}
        		else
        		{
        			update1(rson(pre),rson(rt),mid+1,r,pos);
        		}
        	}
        	void update2(int &rt,int l,int r,int pos,int val)
        	{
        		rt=(rt==0)?build_rt():rt;
        		tree[rt].sum+=val;
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		if(pos<=mid)
        		{
        			update2(lson(rt),l,mid,pos,val);
        		}
        		else
        		{
        			update2(rson(rt),mid+1,r,pos,val);
        		}
        	}
        	int query(int rt1[],int len1,int rt2[],int len2,int l,int r,int k)
        	{
        		if(l==r)
        		{
        			return l;
        		}
        		int mid=(l+r)/2,sum=0;
        		for(int i=1;i<=len2;i++)
        		{
        			sum+=tree[lson(rt2[i])].sum;
        		}
        		for(int i=1;i<=len1;i++)
        		{
        			sum-=tree[lson(rt1[i])].sum;
        		}
        		if(k<=sum)
        		{
        			for(int i=1;i<=len2;i++)
        			{
        				rt2[i]=lson(rt2[i]);
        			}
        			for(int i=1;i<=len1;i++)
        			{
        				rt1[i]=lson(rt1[i]);
        			}   
        			return query(rt1,len1,rt2,len2,l,mid,k);
        		}
        		else
        		{
        			for(int i=1;i<=len2;i++)
        			{
        				rt2[i]=rson(rt2[i]);
        			}
        			for(int i=1;i<=len1;i++)
        			{
        				rt1[i]=rson(rt1[i]);
        			}   
        			return query(rt1,len1,rt2,len2,mid+1,r,k-sum);
        		}
        	}
        }T;
        struct BIT
        {
        	int rt[2][32];
        	int lowbit(int x)
        	{
        		return (x&(-x));
        	}
        	void add(int n,int x,int val)
        	{
        		int pos=lower_bound(b+1,b+1+b[0],a[x])-b;
        		for(int i=x;i<=n;i+=lowbit(i))
        		{
        			T.update2(T.root2[i],1,b[0],pos,val);
        		}
        	}
        	int query(int l,int r,int k)
        	{
        		memset(rt,0,sizeof(rt));
        		for(int i=r;i>=1;i-=lowbit(i))
        		{
        			rt[1][0]++;
        			rt[1][rt[1][0]]=T.root2[i];
        		}
        		rt[1][0]++;
        		rt[1][rt[1][0]]=T.root1[r];
        		for(int i=l-1;i>=1;i-=lowbit(i))
        		{
        			rt[0][0]++;
        			rt[0][rt[0][0]]=T.root2[i];
        		}
        		rt[0][0]++;
        		rt[0][rt[0][0]]=T.root1[l-1];
        		return T.query(rt[0],rt[0][0],rt[1],rt[1][0],1,b[0],k);
        	}
        }B;
        int main()
        {
        	int n,m,i;
        	cin>>n>>m;
        	for(i=1;i<=n;i++)
        	{
        		cin>>a[i];
        		b[0]++;
        		b[b[0]]=a[i];
        	}
        	for(i=1;i<=m;i++)
        	{
        		cin>>q[i].pd>>q[i].l>>q[i].r;
        		if(q[i].pd=='Q')
        		{
        			cin>>q[i].k;
        		}
        		else
        		{
        			b[0]++;
        			b[b[0]]=q[i].r;
        		}
        	}
        	sort(b+1,b+1+b[0]);
        	b[0]=unique(b+1,b+1+b[0])-(b+1);
        	T.build_tree(T.root1[0],1,b[0]);
        	for(i=1;i<=n;i++)
        	{
        		T.update1(T.root1[i-1],T.root1[i],1,b[0],lower_bound(b+1,b+1+b[0],a[i])-b);
        	}
        	for(i=1;i<=m;i++)
        	{
        		if(q[i].pd=='Q')
        		{
        			cout<<b[B.query(q[i].l,q[i].r,q[i].k)]<<endl;
        		}
        		else
        		{
        			B.add(n,q[i].l,-1);
        			a[q[i].l]=q[i].r;
        			B.add(n,q[i].l,1);
        		}
        	}    
        	return 0;
        }
        
  • 离散化来减小常数。

[AGC016A] Shrinking

CF1675B Make It Increasing

7.23

闲话

  • 上午 @Chen_jr 讲了动态 DP 和回文相关(马拉车和回文自动机); @worldvanquisher 讲了容斥相关,包括子集反演,莫反, minmax 容斥,二项式反演,卡特兰数,反射容斥; @Delov 讲了 WQS 二分,说明天 AC 自动机挪到放假那天 8.3 上午讲,加赛由晚上挪到了早上。 @Delov@Chen_jr 决定晚上讲题时说下对拍。
  • 中午 feifei 查宿。
  • 下午 14:0018:00 @KafuuChinocpp | @Chen_jr 组织了一场模拟赛。
  • 晚上讲题的时候 @Chen_jr 讲了下对拍,给了点关于模拟赛的建议;一些首师附的人来我们机房了,貌似一天 500
  • 晚休 feifei 查宿。

做题纪要

[AGC017D] Game on Tree

UOJ 605. 【UER #9】知识网络

[ARC148A] mod M

[ARC107D] vmber of Multisets

7.24

闲话

  • 上午 7:3011:30 @LYinMX 学长安排了一场加赛。
  • 临吃午饭的时候 feifeifield 来给送雪糕和雪莲。
  • 下午讲加赛的题。下午三点多发现下雨了。
  • huge 说以后体活一周两次,分别是周三、六,时间是 17:3019:00

做题纪要

[ARC149D] Simultaneous Sugoroku

P148. AC

luogu P5490 【模板】扫描线

  • 多倍经验: luogu P8648 [蓝桥杯 2017 省 A] 油漆面积 | luogu P1884 [USACO12FEB] Overplanting S | luogu P2061 [USACO07OPEN] City Horizon S

  • 扫描线板子。

    • 如果用一条竖直直线从左到右扫过整个图形,那么直线上被并集图形覆盖的长度只会在每个矩形的左右边界发生变化。

      • 以下是水平直线扫的过程。图片来自 OI Wiki

    • 整个并集图形可以被分成 2n 段,每一段在直线上覆盖的长度是覆盖的,这段的面积就是覆盖的长度乘以这段的宽度。

    • 线段树维护覆盖的长度,当扫到左边界时标记为 1 ,将这条边加进来;扫到右边界时标记为 1 ,删去这条边。

    • 对坐标进行离散化,此时线段树维护的是坐标的排名,节点所代替的区间 [l,r] 所代表的实际上是 [l,r+1] 这段区间的线段。

    • 坐标间满足 {x1x2y1y2

  • 不会标记永久化,遂写的正常标记下传。线段树统计非 0 个数,记录最小值和最小值的个数,若最小值为 0 最后减一下即可。

    点击查看代码
    struct node
    {
    	ll x,l,r,val;
    }a[200010];
    ll b[200010];
    bool cmp(node a,node b)
    {
    	return (a.x==b.x)?(a.val<b.val):(a.x<b.x);
    }
    struct SMT
    {
    	struct SegmentTree
    	{
    		ll l,r,len,sum,minn,lazy;
    	}tree[800010];
    	ll lson(ll x)
    	{
    		return x*2;
    	}
    	ll rson(ll x)
    	{
    		return x*2+1;
    	}
    	void pushup(ll rt)
    	{
    		tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn);
    		if(tree[lson(rt)].minn<tree[rson(rt)].minn)
    		{
    			tree[rt].sum=tree[lson(rt)].sum;
    		}
    		if(tree[lson(rt)].minn==tree[rson(rt)].minn)
    		{
    			tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;
    		}
    		if(tree[lson(rt)].minn>tree[rson(rt)].minn)
    		{
    			tree[rt].sum=tree[rson(rt)].sum;
    		}
    	}
    	void build(ll rt,ll l,ll r)
    	{
    		tree[rt].l=l;
    		tree[rt].r=r;
    		tree[rt].len=b[r+1]-b[l];
    		if(l==r)
    		{
    			tree[rt].sum=tree[rt].len;
    			return;
    		}
    		int mid=(l+r)/2;
    		build(lson(rt),l,mid);
    		build(rson(rt),mid+1,r);
    		pushup(rt);
    	}
    	void pushdown(ll rt)
    	{
    		if(tree[rt].lazy!=0)
    		{
    			tree[lson(rt)].lazy+=tree[rt].lazy;
    			tree[rson(rt)].lazy+=tree[rt].lazy;
    			tree[lson(rt)].minn+=tree[rt].lazy;
    			tree[rson(rt)].minn+=tree[rt].lazy;
    			tree[rt].lazy=0;
    		}
    	}
    	void update(ll rt,ll x,ll y,ll val)
    	{
    		if(x<=tree[rt].l&&tree[rt].r<=y)
    		{
    			tree[rt].lazy+=val;
    			tree[rt].minn+=val;
    			return;
    		}
    		pushdown(rt);
    		ll mid=(tree[rt].l+tree[rt].r)/2;
    		if(x<=mid)
    		{
    			update(lson(rt),x,y,val);
    		}
    		if(y>mid)
    		{
    			update(rson(rt),x,y,val);
    		}
    		pushup(rt);
    	}   
    	ll query(ll rt,ll x,ll y)
    	{
    		if(x<=tree[rt].l&&tree[rt].r<=y)
    		{
    			return tree[rt].len-tree[rt].sum*(tree[rt].minn<=0);
    		}
    		pushdown(rt);
    		ll mid=(tree[rt].l+tree[rt].r)/2,ans=0;
    		if(x<=mid)
    		{
    			ans+=query(lson(rt),x,y);
    		}
    		if(y>mid)
    		{
    			ans+=query(rson(rt),x,y);
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    	ll n,m=0,x1,y1,x2,y2,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>x1>>y1>>x2>>y2;
    		m++;
    		a[m].x=x1;
    		a[m].l=y1;
    		a[m].r=y2;
    		a[m].val=1;
    		b[m]=y1;
    		m++;
    		a[m].x=x2;
    		a[m].l=y1;
    		a[m].r=y2;
    		a[m].val=-1;
    		b[m]=y2;
    	}
    	sort(b+1,b+1+m);
    	b[0]=unique(b+1,b+1+m)-(b+1);
    	for(i=1;i<=m;i++)
    	{
    		a[i].l=lower_bound(b+1,b+b[0]+1,a[i].l)-b;
    		a[i].r=lower_bound(b+1,b+b[0]+1,a[i].r)-b;
    	}
    	sort(a+1,a+1+m,cmp);
    	T.build(1,1,b[0]-1);
    	for(i=1;i<=m-1;i++)
    	{
    		if(a[i].l<=a[i].r-1)
    		{
    			T.update(1,a[i].l,a[i].r-1,a[i].val);
    		}
    		ans+=(a[i+1].x-a[i].x)*T.query(1,1,b[0]-1);//宽度乘以长度
    	}
    	cout<<ans<<endl;
    	return 0;
    }       
    

P147. AB

luogu P6949 [ICPC2018 WF] Triangles

T3682. 七负我

7.25

闲话

  • 上午 7:3011:30 @KafuuChinocpp | @Chen_jr 学长安排了一场模拟赛。
  • 还在下雨。
  • 下午和 GXYZ 的线上讲题,不知道对面有没有 bobo
  • 教练把考勤数据放到了 OJ 主页,不知道想干啥,估计是想让我们在到位上找找差距?但我既不迟到,又不早退,真就纯唐是吧。

做题纪要

POJ2989 All Friends

  • 最大团板子。

    点击查看代码
    int dis[150][150],R[150][150],P[150][150],X[150][150],cnt;
    void Bron_Kerbosch(int dep,int r,int p,int x)
    {
    	if(p==0&&x==0)
    	{
    		cnt++;
    	}
    	else
    	{
    		int u=P[dep][1];
    		for(int i=1;i<=p;i++)
    		{
    			if(dis[u][P[dep][i]]==0)
    			{
    				for(int j=1;j<=r;j++)
    				{
    					R[dep+1][j]=R[dep][j];
    				}
    				R[dep+1][r+1]=P[dep][i];
    				int np=0,nx=0;
    				for(int j=1;j<=p;j++)
    				{
    					if(dis[P[dep][i]][P[dep][j]]!=0)
    					{
    						np++;
    						P[dep+1][np]=P[dep][j];
    					}
    				}
    				for(int j=1;j<=x;j++)
    				{
    					if(dis[P[dep][i]][X[dep][j]]!=0)
    					{
    						nx++;
    						X[dep+1][nx]=X[dep][j];
    					}
    				}
    				Bron_Kerbosch(dep+1,r+1,np,nx);
    				x++;
    				X[dep][x]=P[dep][i];
    				P[dep][i]=0;
    				if(cnt>1000)
    				{
    					return;
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	int n,m,u,v,i;
    	while(cin>>n>>m)
    	{
    		memset(dis,0,sizeof(dis));
    		cnt=0;
    		for(i=1;i<=m;i++)
    		{
    			cin>>u>>v;
    			dis[u][v]=dis[v][u]=1;
    		}
    		for(i=1;i<=n;i++)
    		{
    			P[1][i]=i;
    		}
    		Bron_Kerbosch(1,0,n,0);
    		if(cnt>1000)
    		{
    			cout<<"Too many maximal sets of friends."<<endl;
    		}
    		else
    		{
    			cout<<cnt<<endl;
    		}
    	}
    	return 0;
    }
    

CF1844B Permutations & Primes

[ARC116E] Spread of Information

luogu P3402 可持久化并查集

  • 可持久化并查集板子。

    • 路径压缩的并查集的时间复杂度在均摊情况下单次是 O(logn) ,但在可持久化后无法保证时间复杂度。
    • 考虑按秩合并的并查集,定义秩为未路径压缩下树的深度,在合并时把秩较小的树根作为秩较大的树根的子节点。
      • depxdeoy ,让 y 成为 x 的父亲节点,若 depx=depydepy 加一。
    • 可持久化数组维护父亲节点 {fa} 和深度 {dep} ,要进行两次修改。
    点击查看代码
    int a[200010];
    struct PDS_SMT
    {
    	int root[200010],rt_sum=0;
    	struct SegmentTree
    	{
    		int ls,rs,fa,dep;
    	}tree[200010<<5];
    	#define lson(rt) tree[rt].ls
    	#define rson(rt) tree[rt].rs
    	int build_rt()
    	{
    		rt_sum++;
    		return rt_sum;
    	}
    	void build_tree(int &rt,int l,int r)
    	{
    		rt=build_rt();
    		if(l==r)
    		{
    			tree[rt].fa=l;
    			tree[rt].dep=0; 
    			return;
    		}
    		int mid=(l+r)/2;
    		build_tree(lson(rt),l,mid);
    		build_tree(rson(rt),mid+1,r);
    	}
    	void update(int pre,int &rt,int l,int r,int pos,int val,int pd)
    	{
    		rt=build_rt();
    		tree[rt]=tree[pre];
    		if(l==r)
    		{
    			tree[rt].fa=(pd==0)?val:tree[rt].fa;
    			tree[rt].dep+=(pd==1)*val;
    			return;
    		}
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			update(lson(pre),lson(rt),l,mid,pos,val,pd);
    		}
    		else
    		{
    			update(rson(pre),rson(rt),mid+1,r,pos,val,pd);
    		}
    	}
    	int query(int rt,int l,int r,int pos)
    	{
    		if(l==r)
    		{
    			return rt;//记录是线段树上哪个节点
    		}
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			return query(lson(rt),l,mid,pos);
    		}
    		else
    		{
    			return query(rson(rt),mid+1,r,pos);
    		}
    	}
    }T;
    struct PDS_DSU
    {
    	int find(int rt,int x,int n)	
    	{
    		int fa=T.query(rt,1,n,x);//向上跳
    		return (T.tree[fa].fa==x)?fa:find(rt,T.tree[fa].fa,n);
    	}
    	void merge(int pre,int &rt,int x,int y,int n)
    	{
    		x=find(rt,x,n);
    		y=find(rt,y,n);
    		if(T.tree[x].fa!=T.tree[y].fa)
    		{
    			if(T.tree[x].dep>T.tree[y].dep)
    			{
    				swap(x,y);
    			}
    			T.update(pre,rt,1,n,T.tree[x].fa,T.tree[y].fa,0);//修改 fa
    			if(T.tree[x].dep==T.tree[y].dep)
    			{
    				T.update(rt,rt,1,n,T.tree[y].fa,1,1);//加 dep
    			}
    		}
    	}
    }D;
    int main()
    {
    	int n,m,pd,u,v,i;
    	cin>>n>>m;
    	T.build_tree(T.root[0],1,n);
    	for(i=1;i<=m;i++)
    	{
    		cin>>pd>>u;
    		if(pd==1)
    		{
    			cin>>v;
    			T.root[i]=T.root[i-1];
    			D.merge(T.root[i-1],T.root[i],u,v,n);
    		}
    		if(pd==2)
    		{
    			T.root[i]=T.root[u];
    		}
    		if(pd==3)
    		{
    			cin>>v;
    			T.root[i]=T.root[i-1];
    			cout<<(T.tree[D.find(T.root[i],u,n)].fa==T.tree[D.find(T.root[i],v,n)].fa)<<endl;
    		}
    	}
    	return 0;
    }
    

[ABC302Ex] Ball Collector

7.26

闲话

  • 上午 7:3011:30 @joke3579 学长安排了一场模拟赛。
  • 午休 feifei 查宿。
  • 下午先分瓜,然后讲题。
  • 不知道为啥 feifei 把我们交出去测脊柱侧弯,测的人看不出来源,二维码表上写的是 2021xx 班,附有学校和人数。
  • 晚休 feifei 查宿。

做题纪要

[ABC093C] Same Integers

CF1706E Qpwoeirut and Vertices

[AGC056D] Subset Sum Game

7.27

闲话

  • 上午 7:3011:30 @Delov 学长安排了一场模拟赛。
  • 午休 feifei 查宿。
  • 下午讲题。体活前分了个梨,然后在机房 。吃完饭看 @Delov@joke3579 学长
  • 晚上组织看《抓娃娃》,然后学长给分了汽水,貌似是 @LYinMX 学长带的。
  • 看完电影秒进入改题状态。

做题纪要

luogu P7028 [NWRRC2017] Joker

[ABC285F] Substring of Sorted String

JOISC 2014 Day1 ラーメンの食べ比べ

[ABC290G] Edge Elimination

7.28

闲话

  • 早上体活,仍然是没有起床铃。
  • 上午 7:3011:30 @worldvanquisher 学长安排了一场模拟赛。
  • 午休 huge 查宿。
  • 下午讲题。分瓜前 huge 抓了对面首师附一个小孩哥睡觉。临吃晚饭的时候 huge 分瓜。
    • 爆典现场

      huge 在看 @KafuuChinocpp 的画)

      huge :你看都在一个机房,人家学长真是多才多艺!

      huge:这是画的什么?是原神里的角色吗?

      (诸生狂笑不止)

      huge:你们难道不知道吗?

  • 晚休 feifei 查宿。

做题纪要

luogu P4459 [BJOI2018] 双人猜数游戏

P167. 高松灯

luogu P3216 [HNOI2011] 数学作业

luogu P4195 【模板】扩展 BSGS/exBSGS

  • 多倍经验:SP3105 MOD - Power Modulo Inverted

  • exBSGS 板子。

    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
    	if(b==0)
    	{
    		x=1;
    		y=0;
    		return a;
    	}
    	else
    	{
    		ll d=exgcd(b,a%b,y,x);
    		y-=a/b*x;
    		return d;
    	}
    }
    ll inv(ll a,ll p)
    {
    	ll x,y;
    	exgcd(a,p,x,y);
    	return (x%p+p)%p;
    }
    ll bsgs(ll a,ll b,ll p)
    {
    	if(1%p==b%p)
    	{
    		return 0;
    	}
    	else
    	{
    		unordered_map<ll,ll>vis;
    		ll k=sqrt(p)+1,i,sum=1;
    		for(i=0;i<=k-1;i++)
    		{
    			b=(i==0)?b:b*a%p;
    			vis[b]=i;
    		}
    		a=qpow(a,k,p);
    		for(i=0;i<=k;i++)
    		{
    			sum=(i==0)?sum:sum*a%p;
    			if(vis.find(sum)!=vis.end())
    			{
    				if(i*k-vis[sum]>=0)
    				{
    					return i*k-vis[sum];
    				}
    			}
    		}
    		return -1;
    	}
    }
    ll exbsgs(ll a,ll b,ll p)
    {
    	b%=p;//防止后面判 b%d==0 时出错
    	if(b==1||p==1)//特判 0 的情况
    	{
    		return 0;
    	}
    	else
    	{
    		ll x,y,d=exgcd(a,p,x,y),k=0,mul=1;
    		while(d!=1)
    		{
    			if(b%d==0)
    			{
    				k++;
    				b/=d;
    				p/=d;
    				mul=(a/d)*mul%p;
    				if(mul==b)//已经是答案
    				{
    					return k;
    				}
    				else
    				{
    					d=exgcd(a,p,x,y);
    				}
    			}
    			else
    			{
    				return -1;
    			}
    		}
    		ll ans=bsgs(a,b*inv(mul,p)%p,p);
    		return (ans!=-1)*k+ans;
    	}
    }
    int main()
    {
    	ll a,b,p,ans;
    	while(scanf("%lld%lld%lld",&a,&p,&b)==3)
    	{
    		if(a==0&&b==0&&p==0)
    		{
    			break;
    		}
    		else
    		{
    			ans=exbsgs(a,b,p);
    			if(ans==-1)
    			{
    				printf("No Solution\n");
    			}
    			else
    			{
    				printf("%lld\n",ans);
    			}
    		}
    	}
    	return 0;
    }
    

luogu P4861 按钮

  • 因为要找的是第二个使 ax1(modp) 成立的解,所以把有关 x=0 的特判删了即可。

    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
    	if(b==0)
    	{
    		x=1;
    		y=0;
    		return a;
    	}
    	else
    	{
    		ll d=exgcd(b,a%b,y,x);
    		y-=a/b*x;
    		return d;
    	}
    }
    ll inv(ll a,ll p)
    {
    	ll x,y;
    	exgcd(a,p,x,y);
    	return (x%p+p)%p;
    }
    ll bsgs(ll a,ll b,ll p)
    {
    	unordered_map<ll,ll>vis;
    	ll k=sqrt(p)+1,i,sum=1;
    	for(i=0;i<=k-1;i++)
    	{
    		b=(i==0)?b:b*a%p;
    		vis[b]=i;
    	}
    	a=qpow(a,k,p);
    	for(i=0;i<=k;i++)
    	{
    		sum=(i==0)?sum:sum*a%p;
    		if(vis.find(sum)!=vis.end())
    		{
    			if(i*k-vis[sum]>0)
    			{
    				return i*k-vis[sum];
    			}
    		}
    	}
    	return -1;
    }
    ll exbsgs(ll a,ll b,ll p)
    {
    	b%=p;
    	ll x,y,d=exgcd(a,p,x,y),k=0,mul=1;
    	while(d!=1)
    	{
    		if(b%d==0)
    		{
    			k++;
    			b/=d;
    			p/=d;
    			mul=(a/d)*mul%p;
    			if(mul==b)
    			{
    				return k;
    			}
    			else
    			{
    				d=exgcd(a,p,x,y);
    			}
    		}
    		else
    		{
    			return -1;
    		}
    	}
    	ll ans=bsgs(a,b*inv(mul,p)%p,p);
    	return (ans!=-1)*k+ans;
    
    }
    int main()
    {
    	ll a,b=1,p,ans;
    	cin>>p>>a;
    	ans=exbsgs(a,b,p);
    	if(ans==-1)
    	{
    		cout<<"Let's go Blue Jays!"<<endl;
    	}
    	else
    	{
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

UVA11526 H(n)

  • 多倍经验: [ABC230E] Fraction Floor Sum

  • 整除分块板子。

    点击查看代码
    int main()
    {
    	ll t,n,ans,l,r,i;
    	cin>>t;
    	for(i=1;i<=t;i++)
    	{
    		cin>>n;
    		ans=0;
    		for(l=1,r;l<=n;l=r+1)
    		{
    			r=(n/l>=1)?min(n/(n/l),n):n;
    			ans+=(n/l)*(r-l+1);
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

luogu P3935 Calculating

  • f(x) 实际上是在 x 的约数个数和。

  • 考虑进行前缀和,将式子拆成 i=1rf(i)i=1l1f(i) 的形式。

  • 统计约数个数不太可做,考虑统计倍数个数,即 i=1nd=1i[d|i]=d=1ni=dn[d|i]=d=1nnd

  • 然后就是整除分块板子了。

    点击查看代码
    const ll p=998244353;
    ll ask(ll n)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=(n/l>=1)?min(n/(n/l),n):n;
    		ans=(ans+(n/l)*(r-l+1)%p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll l,r;
    	cin>>l>>r;
    	cout<<(ask(r)-ask(l-1)+p)%p<<endl;
    	return 0;
    }
    

luogu P2424 约数和

  • 多乘个 d 即可。

    点击查看代码
    ll ask(ll n)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=(n/l>=1)?min(n/(n/l),n):n;
    		ans+=(n/l)*(r-l+1)*(l+r)/2;
    	}
    	return ans;
    }
    int main()
    {
    	ll l,r;
    	cin>>l>>r;
    	cout<<ask(r)-ask(l-1)<<endl;
    	return 0;
    }
    

luogu P2261 [CQOI2007] 余数求和

luogu P2260 [清华集训2012] 模积和

  • 二维整除分块板子。

  • nm

  • 将式子拆开,有 i=1nj=1m[ij]×(nmodi)×(mmodj)=i=1nj=1m(nmodi)×(mmodj)i=1n(nmodi)×(mmodi)=i=1n(nmodi)j=1m(mmodj)i=1n(nmodi)×(mmodi)=i=1n(nni×i)j=1m(mmj×j)i=1n(nni×i)×(mmi×i)=(n2i=1nni×i)×(m2j=1mmj×j)i=1n(nmni×immi×in+ni×mi×i2)=(n2i=1nni×i)×(m2j=1mmj×j)n2m+mi=1nni×i+ni=1nmi×ii=1nni×mi×i2

  • 难点在于求 i=1nni×mi×i2

  • 同时出现两个向下取整只会增加块数,但仍是 O(n) 级别的不会影响复杂度。

  • i=1ni2=n(n+1)(2n+1)6 ,维护前缀和后相减即可。

    点击查看代码
    const ll p=19940417;
    ll inv2,inv6;
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
    	if(b==0)
    	{
    		x=1;
    		y=0;
    		return a;
    	}
    	else
    	{
    		ll d=exgcd(b,a%b,y,x);
    		y-=a/b*x;
    		return d;
    	}
    }
    ll inv(ll a,ll p)
    {
    	ll x,y;
    	exgcd(a,p,x,y);
    	return (x%p+p)%p;
    }
    ll sum1(ll x)
    {
    	return x*(x+1)%p*inv2%p;
    }
    ll sum2(ll x)
    {
    	return x*(x+1)%p*(2*x+1)%p*inv6%p;
    }
    ll ask1(ll n,ll k)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=(k/l>=1)?min(k/(k/l),n):n;
    		ans=(ans+((k/l)%p)*((sum1(r)-sum1(l-1)+p)%p)%p)%p;
    	}
    	return ans;
    }
    ll ask2(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans=(ans+(((n/l)%p)*((m/l)%p)%p)*((sum2(r)-sum2(l-1)+p)%p)%p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll n,m,ans;
    	cin>>n>>m;
    	if(n>m)
    	{
    		swap(n,m);
    	}
    	inv2=inv(2,p);
    	inv6=inv(6,p);
    	ans=(n*n%p-ask1(n,n)+p)%p;
    	ans=ans*((m*m%p-ask1(m,m)+p)%p)%p;
    	ans=(ans-n*n%p*m%p+p)%p;
    	ans=(ans+ask1(n,n)*m%p)%p;
    	ans=(ans+ask1(n,m)*n%p)%p;
    	ans=(ans-ask2(n,m)+p)%p;
    	cout<<ans<<endl;
    	return 0;
    }
    

luogu P2257 YY的GCD

  • 多倍经验: SP4491 PGCD - Primes in GCD Table

  • nm

  • 推式子,有 i=1nj=1m[gcd(i,j)P]=kPni=1nj=1m[gcd(i,j)=k]=kPni=1nkj=1mk[gcd(i,j)=1]=kPni=1nkj=1mkd|gcd(i,j)μ(d)=kPnd=1nkμ(d)i=1nk[d|i]j=1mk[d|j]=kPnd=1nkμ(d)nkdmkd=T=1nnTmTkP,k|Tμ(Tk)

  • 预处理 kP,k|Tμ(Tk) ,接着数论分块维护。

    点击查看代码
    ll prime[10000010],vis[10000010],sum[10000010],miu[10000010],f[10000010],len;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				break;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=len;i++)
    	{
    		for(ll j=1;prime[i]*j<=n;j++)
    		{
    			f[prime[i]*j]+=miu[j];
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+f[i];
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	isprime(10000000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

7.29

闲话

  • 早上看见 miaomiao 回来了,穿着 NOI40 周年纪念的 T 恤。
  • 上午 7:3011:30 @KafuuChinocpp 学长安排了一场模拟赛。
  • 午休 miaomiao 查宿,挨个宿舍问几个人和空调能不能用。
  • 下午讲题。临 16:00 的时候 hugemiaomiao 突然说让我们休息 15min ,尽管机房众人都不为所动, hugemiaomiao 见状也不好说啥。

做题纪要

UVA10214 树林里的树 Trees in a Wood.

  • 所有树数量为 (2n+1)(2m+1)1

  • 仅计算一个象限的答案,然后乘 4 再加上 (0,1),(0,1),(1,0),(1,0) 的贡献。

  • nm

  • 推式子,有 i=1nj=1m[gcd(i,j)=1]=i=1nj=1md|gcd(i,j)μ(d)=d=1nμ(d)i=1n[d|i]j=1m[d|j]=d=1nμ(d)ndmd

  • 预处理 μ 的前缀和,整除分块维护。

    点击查看代码
    ll prime[2000010],vis[2000010],sum[2000010],miu[2000010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+miu[i];
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll n,m;
    	isprime(2000000);
    	while(cin>>n>>m)
    	{
    		if(n==0&&m==0)
    		{
    			break;
    		}
    		else
    		{
    			if(n>m)
    			{
    				swap(n,m);
    			}
    			printf("%.7lf\n",1.0*(ask(n,m)*4+4)/((2*n+1)*(2*m+1)-1));
    		}
    	}
    	return 0;
    }
    

UVA12888 Count LCM

  • lcm(i,j)=ij 等价于 gcd(i,j)=1 ,然后就和上题一样了。

    点击查看代码
    ll prime[1000010],vis[1000010],sum[1000010],miu[1000010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+miu[i];
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	isprime(1000000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

SP3871 GCDEX - GCD Extreme

  • 多倍经验: UVA11417 GCD | UVA11424 GCD - Extreme (I) | UVA11426 拿行李(极限版) GCD - Extreme (II)

  • i=1nj=1ngcd(i,j)=i=1nj=1i1gcd(i,j)+i=1nj=i+1ngcd(i,j)+i=1ngcd(i,i) ,有 i=1nj=1i1gcd(i,j)=i=1nj=i+1ngcd(i,j)=i=1nj=1ngcd(i,j)i=1ngcd(i,i)2=i=1nj=1ngcd(i,j)n(n+1)22

  • 推式子,有 i=1nj=1ngcd(i,j)=k=1nki=1nj=1n[gcd(i,j)=k]=k=1nki=1nkj=1nk[gcd(i,j)=1]=k=1nki=1nkj=1nkd|gcd(i,j)μ(d)=k=1nkd=1nkμ(d)i=1nk[d|i]j=1nk[d|j]=k=1nkd=1nkμ(d)nkd2=T=1nnT2k|Tμ(Tk)×k=T=1nnT2φ(T)

  • 预处理出 φ 的前缀和,整除分块维护。

    点击查看代码
    ll sum[1000010];
    int prime[1000010],phi[1000010],len=0;
    bool vis[1000010];
    void isprime(int n)
    {
    	memset(vis,0,sizeof(vis));
    	phi[1]=1;
    	for(int i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			phi[i]=i-1;
    		}
    		for(int j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				phi[i*prime[j]]=phi[i]*prime[j];
    				break;
    			}
    			else	
    			{
    				phi[i*prime[j]]=phi[i]*(prime[j]-1);
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+phi[i];
    	}
    }
    ll ask(ll n)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=(n/l>=1)?min(n/(n/l),n):n;
    		ans+=(n/l)*(n/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll n;
    	isprime(1000000);
    	while(scanf("%lld",&n))
    	{
    		if(n==0)
    		{
    			break;
    		}
    		else
    		{
    			printf("%lld\n",(ask(n)-n*(n+1)/2)/2);
    		}
    	}
    	return 0;
    }
    

P3455 [POI2007] ZAP-Queries

  • 多倍经验: luogu P4450 双亲数

  • nm

  • 推式子,有 i=1nj=1m[gcd(i,j)=k]=i=1nkj=1mk[gcd(i,j)=1]=i=1nkj=1mkd|gcd(i,j)μ(d)=d=1nkμ(d)i=1nk[d|i]j=1mk[d|j]=d=1nkμ(d)nkdmkd

  • 预处理出 μ 的前缀和,整除分块维护。

    点击查看代码
    ll prime[50010],vis[50010],miu[50010],sum[50010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				break;
    			}
    			else 
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+miu[i];
    	}
    }
    ll ask(ll n,ll m,ll k)
    {
    	ll ans=0,l,r;
    	n/=k;
    	m/=k;
    	if(n>m)
    	{
    		swap(n,m);
    	}
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,k,i;
    	cin>>t;
    	isprime(50000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m>>k;
    		cout<<ask(n,m,k)<<endl;
    	}
    	return 0;
    }
    

luogu P2522 [HAOI2011] Problem b

  • 将式子进行二维差分,有 i=abj=cd[gcd(i,j)=k]=i=1bj=1d[gcd(i,j)=k]i=1a1j=1d[gcd(i,j)=k]i=1bj=1c1[gcd(i,j)=k]+i=1a1j=1c1[gcd(i,j)=k]

  • 然后就和上题一样了。

    点击查看代码
    ll prime[50010],vis[50010],miu[50010],sum[50010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+miu[i];
    	}
    }
    ll ask(ll n,ll m,ll k)
    {
    	ll ans=0,l,r;
    	n/=k;
    	m/=k;
    	if(n>m)
    	{
    		swap(n,m);
    	}
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,a,b,c,d,k,i;
    	cin>>t;
    	isprime(50000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>a>>b>>c>>d>>k;
    		cout<<ask(b,d,k)-ask(a-1,d,k)-ask(b,c-1,k)+ask(a-1,c-1,k)<<endl;
    	}
    	return 0;
    }
    

luogu P6156 简单题

luogu P10947 Sightseeing

[ABC274F] Fishing

[ARC148E] ≥ K

7.30

闲话

  • 上午 miaomiao 进来说了下模拟赛的题尽量改,不能不改。
  • 机房又断水了。
  • 下午 miaomiao 在和 huge 小声交流后,突然出去了又拿着一包不知道什么东西进来了。 miaomiao 称这是“抽奖,要给生活找点乐子”,实际上是在袋子里选雪糕。“小可爱( huge 所称 )”家长送来了桃,说让我们等吃饭的时候取食堂洗洗再吃。最后 huge 手里剩了两三根雪糕,决定给每个机房再分一根,想吃的人两两进行猜拳,最后胜出的人给发雪糕。
  • 临吃晚饭前, huge 跟我们说下面有领导视察,让我们等会儿再去吃饭。 18:12 时才让下去吃饭,然后 huge 站在楼下挨个说从哪里到食堂。

做题纪要

luogu P3379 【模板】最近公共祖先(LCA)

  • DFS 序求 LCA 板子。

    • u,vLCAd
    • DFS 序中,祖先节点一定比子孙节点先遍历到,相应的时间戳 dfn 也靠前。
    • dfnudfnv
    • u 不是 v 的祖先, DFS 的顺序一定是从 du ,再回到 d ,再到 v 。取 dv 方向上的第一个节点 v ,一定有 dfnudfnvdfnv ,且 vuvDFS 序中深度最小的点。但其他点只要满足深度最小就能满足题意,所以在 [dfnu,dfnv] 中可以找任意一个深度最小的点,记录其父亲。
      • 特判 u=v 时直接返回 u
    • uv 的祖先时,令查找区间为 [dfnu+1,dfnv] 即可。
    • 同样,若 u 不是 v 的祖先, u 一定不等于 v ,查找区点少一个 dfnu 并不影响答案,也将查找区间写作 [dfnu+1,dfnv] 即可。
    • 实际代码中,为避免记录每个节点的父亲和深度,我们可以直接在 ST 表的底层记录父亲,合并时取 dfn 较小的那个进行转移。
    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[1000010];
    int head[1000010],dfn[1000010],cnt=0,tot=0;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    int sx_min(int x,int y)
    {
    	return dfn[x]<dfn[y]?x:y;
    }
    struct ST
    {
    	int fminn[1000010][25];
    	void init(int n)
    	{
    		for(int j=1;j<=log2(n);j++)
    		{
    			for(int i=1;i+(1<<j)-1<=n;i++)
    			{
    				fminn[i][j]=sx_min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]);	
    			}
    		}
    	}
    	int query(int l,int r)
    	{
    		int t=log2(r-l+1);
    		return sx_min(fminn[l][t],fminn[r-(1<<t)+1][t]);
    	}
    }T;
    void dfs(int x,int fa)
    {
    	tot++;
    	dfn[x]=tot;
    	T.fminn[tot][0]=fa;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    		}
    	}
    }
    int lca(int u,int v)
    {
    	if(dfn[u]>dfn[v])
    	{	
    		swap(u,v);
    	}
    	return (dfn[u]==dfn[v])?u:T.query(dfn[u]+1,dfn[v]);
    }
    int main()
    {
    	int n,m,rt,u,v,i;
    	cin>>n>>m>>rt;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		add(v,u);
    	}
    	dfs(rt,0);
    	T.init(n);
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v;
    		cout<<lca(u,v)<<endl;
    	}
    	return 0;
    }
    

luogu P9340 [JOISC 2023 Day3] Tourism

luogu P3327 [SDOI2015] 约数个数和

  • 大力结论,有 d(ij)=x|iy|j[gcd(x,y)=1]

    • 感性理解:设 ij 的一个质因子 pi 中的指数为 a ,在 j 中的指数为 b 。在选择配对时,钦定要选的指数 ka 时都从 i 里选,否则将 i 取光再从 j 里选 ka 个。接着钦定已经将 i 取光,就保证了取出的两个数互质。
  • nm

  • 推式子, 有 i=1nj=1mx|iy|i[gcd(x,y)=1]=x=1ny=1m[gcd(x,y)=1]i=1n[x|i]y=1m[y|j]=x=1ny=1m[gcd(x,y)=1]nxmy=i=1nj=1mnimjd|gcd(i,j)μ(d)=d=1nμ(d)i=1nj=1m[d|gcd(i,j)]nimj=d=1nμ(d)i=1ndj=1mdnidmjd=d=1nμ(d)i=1ndnidj=1mdmjd

  • 整除分块预处理 f(n)=i=1nni ,然后再套个整除分块求答案即可。

    点击查看代码
    ll prime[50010],vis[50010],miu[50010],sum[50010],f[50010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				break;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+miu[i];
    		for(ll l=1,r;l<=i;l=r+1)
    		{
    			r=(i/l>=1)?min(i/(i/l),i):i;
    			f[i]+=(i/l)*(r-l+1);
    		}
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=f[n/l]*f[m/l]*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	isprime(50000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

luogu P3312 [SDOI2014] 数表

  • i 行第 j 列的数值为 d|gcd(i,j)d=σ(gcd(i,j))

  • nm

  • 推式子,有 i=1nj=1m[σ(gcd(i,j))a]σ(gcd(i,j))=k=1ni=1nj=1m[gcd(i,j)=k]×[σ(k)a]σ(k)=k=1n[σ(k)a]σ(k)i=1nkj=1mk[gcd(i,j)=1]=k=1n[σ(k)a]σ(k)i=1nkj=1mkd|gcd(i,j)μ(d)=k=1n[σ(k)a]σ(k)d=1nμ(d)i=1nk[d|i]j=1mk[d|j]=k=1n[σ(k)a]σ(k)d=1nμ(d)nkdmkd=T=1nnTmTk|T[σ(k)a]σ(k)μ(Tk)

  • 难点在于怎么维护 f(T)=k|T[σ(k)a]σ(k)μ(Tk) 的前缀和。

  • 线筛预处理出 σ(k),μ(Tk)

  • {a} 离线下来,顺序插入 k 并枚举 Tk ,维护一个支持单点修改,区间查询的树状数组即可。

  • 不知道为啥边加边取模最终结果是负数,但中途不取模最后再取模就过了。

    点击查看代码
    const ll p=1<<31;
    struct node
    {
    	ll n,m,a,id;
    }e[100010];
    ll prime[100010],vis[100010],miu[100010],low[100010],ans[100010],len=0;
    pair<ll,ll>sigma[100010];
    bool cmp(node a,node b)
    {
    	return a.a<b.a;
    }
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	sigma[1]=make_pair(1,1);
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    			sigma[i]=make_pair(i+1,i);
    			low[i]=i;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				low[i*prime[j]]=low[i]*prime[j];
    				if(i==low[i])
    				{
    					sigma[i*prime[j]]=make_pair(sigma[i].first+low[i*prime[j]],i*prime[j]);
    				}
    				else
    				{
    					sigma[i*prime[j]]=make_pair(sigma[i/low[i]].first*sigma[low[i*prime[j]]].first,i*prime[j]);
    				}
    				break;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    				low[i*prime[j]]=prime[j];
    				sigma[i*prime[j]]=make_pair(sigma[i].first*sigma[prime[j]].first,i*prime[j]);
    			}
    		}
    	}
    }
    struct BIT
    {
    	ll c[100010];
    	ll lowbit(ll x)
    	{
    		return (x&(-x));
    	}
    	void add(ll n,ll x,ll val)
    	{
    		for(ll i=x;i<=n;i+=lowbit(i))
    		{
    			c[i]+=val;
    		}
    	}
    	ll getsum(ll x)
    	{
    		ll ans=0;
    		for(ll i=x;i>=1;i-=lowbit(i))
    		{
    			ans+=c[i];
    		}
    		return ans;
    	}
    }T;
    ll ask(ll n,ll m,ll a)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(T.getsum(r)-T.getsum(l-1));
    	}
    	return ans;
    }
    int main()
    {
    	ll q,i,j,k;
    	cin>>q;
    	isprime(100000);
    	sort(sigma+1,sigma+1+100000);
    	for(i=1;i<=q;i++)
    	{
    		cin>>e[i].n>>e[i].m>>e[i].a;
    		e[i].id=i;
    	}
    	sort(e+1,e+1+q,cmp);
    	for(i=1,j=1;i<=q;i++)
    	{
    		while(j<=100000&&sigma[j].first<=e[i].a)
    		{
    			for(k=1;k*sigma[j].second<=100000;k++)
    			{
    				T.add(100000,k*sigma[j].second,sigma[j].first*miu[k]);
    			}
    			j++;
    		}
    		if(e[i].n>e[i].m)
    		{
    			swap(e[i].n,e[i].m);
    		}
    		ans[e[i].id]=ask(e[i].n,e[i].m,e[i].a)%p;
    	}
    	for(i=1;i<=q;i++)
    	{
    		cout<<ans[i]<<endl;
    	}
    	return 0;
    }
    

BZOJ3309 DZY Loves Math

  • nm

  • 推式子,有 i=1nj=1mf(gcd(i,j))=k=1nf(k)i=1nj=1m[gcd(i,j)=k]=k=1nf(k)i=1nkj=1mk[gcd(i,j)=1]=k=1nf(k)i=1nkj=1mkd|gcd(i,j)μ(d)=k=1nf(k)d=1nkμ(d)i=1nk[d|i]j=1mk[d|j]=k=1nf(k)d=1nkμ(d)nkdmkd=T=1nnTmTk|Tf(k)μ(Tk)

  • 问题在于学校 OJ 上跑不了 O(nlogn) ,所以无法暴力线筛 g(n)=d|nf(d)μ(nd) ,考虑分析其性质。

  • T=i=1kpici(i[1,k],piP,ciN),d=i=1kpici(i[1,k],piP,ciN) ,此时 f(d)=maxi=1k{ci}

  • nd 中没有平方质因子时才会对答案产生贡献,即 i[1,k],cici[0,1] 时才会对答案产生贡献。

  • ij,ci<cj 时,除去 cif(d) 的值没有影响。同时 Td 中有 pi 和没有 pi 这个质因子得到的 μ(Td) 是正好相反的,也就无法对答案产生影响。此时 g(T)=0

  • c1=c2==ck 时,同理,只有 i[1,k],ci=ci1 时才会对答案产生贡献,此时 g(T)=μ(i=1kpi)=(1)k+1

  • 线筛过程中记录下 n 的最小质因子的次数 v(n) 判断相不相等即可。

    点击查看代码
    ll prime[10000010],vis[10000010],v[10000010],low[10000010],g[10000010],sum[10000010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			v[i]=g[i]=1;
    			low[i]=i;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)//最小质因子是 prime[j]
    			{
    				v[i*prime[j]]=v[i]+1;
    				low[i*prime[j]]=low[i]*prime[j];
    				if(i==low[i])//只有一个质因子
    				{
    					g[i*prime[j]]=1;
    				}
    				else
    				{
    					g[i*prime[j]]=(v[i/low[i]]==v[low[i]*prime[j]])*-g[i/low[i]];//i/low[i] 与 low[i]*prime[j] 互质
    				}
    				break;
    			}
    			else
    			{
    				v[i*prime[j]]=1;
    				low[i*prime[j]]=prime[j];
    				g[i*prime[j]]=(v[i]==1)*-g[i];//prime[j] 的指数为 1
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+g[i];
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=(n/l)*(m/l)*(sum[r]-sum[l-1]);
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	isprime(10000000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

luogu P3704 [SDOI2017] 数字表格

  • nm

  • 推式子,有 i=1nj=1mfgcd(i,j)=k=1nfki=1nj=1n[gcd(i,j)=k]=k=1nfkd=1nkμ(d)nkdmkd=T=1nk|Tfkμ(Tk)nTmT=T=1n(k|Tfkμ(Tk))nTmT

  • μ(Tk) 的取值只有 {1,0,1} ,处理逆元后暴力线筛。

  • nTmT 需要扩展欧拉定理优化,注意 modφ(p)

    点击查看代码
    const ll p=1000000007,invp=1000000006;
    ll prime[1000010],vis[1000010],miu[1000010],f[1000010],inv_f[1000010],g[1000010],mul[1000010],inv_mul[1000010],len=0;
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=f[1]=inv_f[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		f[i]=(f[i-1]+f[i-2])%p;
    		inv_f[i]=qpow(f[i],p-2,p);
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				break;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    	for(ll i=0;i<=n;i++)
    	{
    		g[i]=1;
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		for(ll j=1;i*j<=n;j++)
    		{
    			if(miu[j]==1)
    			{
    				g[i*j]=g[i*j]*f[i]%p;
    			}
    			else
    			{
    				if(miu[j]==-1)
    				{
    					g[i*j]=g[i*j]*inv_f[i]%p;
    				}
    			}
    		}
    	}
    	mul[0]=inv_mul[0]=1;
    	for(ll i=1;i<=n;i++)
    	{
    		mul[i]=mul[i-1]*g[i]%p;
    		inv_mul[i]=qpow(mul[i],p-2,p);
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=1,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans=ans*qpow(mul[r]*inv_mul[l-1]%p,(n/l)*(m/l)%invp,p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll t,n,m,i;
    	cin>>t;
    	isprime(1000000);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

luogu P4449 于神之怒加强版

  • nm ;因变量重名,以下所写 t 指代原题的 k

  • 推式子,有 i=1nj=1mgcd(i,j)t=k=1nkti=1nj=1m[gcd(i,j)=k]=k=1nktd=1nkμ(d)nkdmkd=T=1nnTmTk|Tktμ(Tk)

  • f(n)=d|ndtμ(nd) 显然是积性函数,整除分块维护。

    • {f(p)=pt1f(p2)=p2tptf(p3)=p3tp2tf(pc)=f(pc1)×pt
      • 实际上是一个能对答案产生贡献的 μ(nd) 的平移过程。
    点击查看代码
    const ll p=1000000007;
    ll prime[5000010],vis[5000010],h[5000010],f[5000010],sum[5000010],len=0;
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    void isprime(ll n,ll k)
    {
    	memset(vis,0,sizeof(vis));
    	h[1]=f[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			h[i]=qpow(i,k,p);
    			f[i]=h[i]-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			h[i*prime[j]]=h[i]*h[prime[j]]%p;
    			if(i%prime[j]==0)
    			{
    				f[i*prime[j]]=f[i]*h[prime[j]]%p;
    				break;
    			}
    			else
    			{
    				f[i*prime[j]]=f[i]*f[prime[j]]%p;
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		sum[i]=(sum[i-1]+f[i]+p)%p;
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)	
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans=(ans+(n/l)*(m/l)%p*((sum[r]-sum[l-1]+p)%p)%p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll t,k,n,m,i;
    	cin>>t>>k;
    	isprime(5000000,k);
    	for(i=1;i<=t;i++)
    	{
    		cin>>n>>m;
    		if(n>m)
    		{
    			swap(n,m);
    		}
    		cout<<ask(n,m)<<endl;
    	}
    	return 0;
    }
    

luogu P1829 [国家集训队] Crash的数字表格 / JZPTAB

  • 多倍经验: SP8099 TABLE - Crash´s vmber table

  • nm

  • 推式子,有 i=1nj=1nlcm(i,j)=i=1nj=1nijgcd(i,j)=k=1n1ki=1nj=1nij[gcd(i,j)=k]=k=1n1ki=1nkj=1mkijk2[gcd(i,j)=1]=k=1nki=1nkj=1mkijd|gcd(i,j)μ(d)=k=1nkd=1nμ(d)i=1nkj=1mkij[d|gcd(i,j)]=k=1nkd=1nμ(d)d2i=1nkdj=1mkdij=k=1nkd=1nμ(d)d2i=1nkdij=1mkdj=T=1nTd|Tμ(d)di=1nTij=1mTj

  • f(n)=d|nμ(d)d 显然是积性函数,线筛筛一下即可。

    • f(pt)=f(p)=1p(tN)
  • 接着维护前缀和后整除分块。

    点击查看代码
    const ll p=20101009;
    ll prime[10000010],f[10000010],s[10000010],len=0;
    bool vis[10000010];
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	f[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			f[i]=1-i;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				f[i*prime[j]]=f[i];
    				break;
    			}
    			else
    			{
    				f[i*prime[j]]=f[i]*f[prime[j]];
    			}
    		}
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		s[i]=(s[i-1]+i)%p;
    		f[i]=(f[i-1]+f[i]*i%p+p)%p;
    	}
    }
    ll ask(ll n,ll m)
    {
    	ll ans=0,l,r;
    	for(l=1,r;l<=n;l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans=(ans+(s[n/l]*s[m/l]%p)*(f[r]-f[l-1]+p)%p)%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll n,m;
    	cin>>n>>m;
    	isprime(max(n,m));
    	if(n>m)
    	{
    		swap(n,m);
    	}
    	cout<<ask(n,m)<<endl;
    	return 0;
    }
    

CF1915G Bicycles

  • 发现 si[1,1000]

  • disx,i 表示到 x 时骑着速度为 i 的最短时间,相应的 dijkstra 过程中也记录下速度进行转移,优先选速度小的进行转移。

  • 最终,有 mini=1n{disn,si} 即为所求。

    点击查看代码
    struct node
    {
    	ll nxt,to,w;
    }e[2010];
    struct quality
    {
    	ll dis,x,s;
    	bool operator < (const quality another) const
    	{
    		return dis>another.dis;
    	}
    }x;
    ll head[2010],s[2010],dis[2010][2010],vis[2010][2010],cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dijkstra(ll st)
    {
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	priority_queue<quality>q;
    	dis[st][s[st]]=0;
    	q.push((quality){dis[st][s[st]],st,s[st]});
    	while(q.empty()==0)
    	{
    		x=q.top();
    		q.pop();
    		if(vis[x.x][x.s]==0)
    		{
    			vis[x.x][x.s]=1;
    			for(ll i=head[x.x];i!=0;i=e[i].nxt)
    			{
    				if(dis[e[i].to][min(x.s,s[e[i].to])]>dis[x.x][x.s]+e[i].w*x.s)
    				{
    					dis[e[i].to][min(x.s,s[e[i].to])]=dis[x.x][x.s]+e[i].w*x.s;
    					q.push((quality){dis[e[i].to][min(x.s,s[e[i].to])],e[i].to,min(x.s,s[e[i].to])});
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ll t,n,m,u,v,w,ans,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cnt=0;
    		ans=0x7f7f7f7f7f7f7f7f;
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		cin>>n>>m;
    		for(i=1;i<=m;i++)
    		{
    			cin>>u>>v>>w;
    			add(u,v,w);
    			add(v,u,w);
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>s[i];
    		}
    		dijkstra(1);
    		for(i=1;i<=n;i++)
    		{
    			ans=min(ans,dis[n][s[i]]);
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

7.31

闲话

  • 上午打模拟赛前 huge 给重启了电脑。 huge8:3010:30 尽量不要出这个机房,外面有领导视察,省里的领导可能来这视察。
  • 上午 7:3011:30 @Chen_jr 学长安排了一场模拟赛。
  • 打完模拟赛 miaomiao 称我们也打了十几套模拟赛了,考试策略和赛后改题方法也应该摸得差不多了。举了 @APJifengc 省选、代码源集训模拟赛、国赛的例子,强调改题不能不改,部分分场上没想出来的也要改,写代码不能嫌麻烦,保持良好的心态,不要太高估其他人的水平,题目难度不一定单调递增。最后 D 我打模拟赛时不认真打模拟赛,打一半就去写专题了。
  • 午休 huge 查宿。
  • 下午讲题,因为 @DanhengYinyuehuge 开回家了,所以是线上讲题。
  • 上体活前给分了个苹果,本来想去书店买本《红楼梦》和《乡土中国》,到了之后发现没开门,遂直接回机房 。吃完饭看 @Delov@joke3579 学长
  • 晚休 miaomiao 查宿。

做题纪要

luogu P3168 [CQOI2015] 任务查询系统

  • 将区间修改单点查询转化为差分后的单点修改单点查询。

  • 对每一时刻建立一棵权值线段树,为减小空间开销使用主席树代替。

    点击查看代码
    ll s[100010],e[100010],p[100010],d[100010];
    vector<ll>pos1[100010],pos2[100010];
    struct PDS_SMT
    {
    	ll root[100010],rt_sum=0;
    	struct SegmentTree
    	{
    		ll ls,rs,cnt,sum;
    	}tree[100010<<6];
    	#define lson(rt) tree[rt].ls
    	#define rson(rt) tree[rt].rs
    	ll build_rt()
    	{
    		rt_sum++;
    		return rt_sum;
    	}
    	void build_tree(ll &rt,ll l,ll r)
    	{
    		rt=build_rt();
    		if(l==r)
    		{
    			return;
    		}
    		ll mid=(l+r)/2;
    		build_tree(lson(rt),l,mid);
    		build_tree(rson(rt),mid+1,r);
    	}
    	void update(ll pre,ll &rt,ll l,ll r,ll pos,ll val)
    	{
    		rt=build_rt();
    		tree[rt]=tree[pre];
    		tree[rt].cnt+=val;
    		tree[rt].sum+=val*d[pos];
    		if(l==r)
    		{
    			return;
    		}
    		ll mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			update(lson(pre),lson(rt),l,mid,pos,val);
    		}
    		else
    		{
    			update(rson(pre),rson(rt),mid+1,r,pos,val);
    		}
    	}
    	ll query(ll rt,ll l,ll r,ll k)
    	{
    		if(l==r)
    		{
    			return tree[rt].sum/tree[rt].cnt*k;
    		}
    		ll mid=(l+r)/2,vm=tree[lson(rt)].cnt;
    		if(k<=vm)
    		{
    			return query(lson(rt),l,mid,k);
    		}
    		else
    		{
    			return query(rson(rt),mid+1,r,k-vm)+tree[lson(rt)].sum;
    		}
    	}
    }T;
    int main()
    {
    	ll n,m,x,a,b,c,ans=1,k,i,j;
    	cin>>m>>n;
    	for(i=1;i<=m;i++)
    	{
    		cin>>s[i]>>e[i]>>p[i];
    		d[i]=p[i];
    		pos1[s[i]].push_back(i);
    		pos2[e[i]+1].push_back(i);
    	}
    	sort(d+1,d+1+m);
    	d[0]=unique(d+1,d+1+m)-(d+1);
    	T.build_tree(T.root[0],1,d[0]);
    	for(i=1;i<=n;i++)
    	{
    		T.root[i]=T.root[i-1];
    		for(j=0;j<pos1[i].size();j++)
    		{
    			T.update(T.root[i],T.root[i],1,d[0],lower_bound(d+1,d+1+d[0],p[pos1[i][j]])-d,1);
    		}
    		for(j=0;j<pos2[i].size();j++)
    		{
    			T.update(T.root[i],T.root[i],1,d[0],lower_bound(d+1,d+1+d[0],p[pos2[i][j]])-d,-1);
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>x>>a>>b>>c;
    		k=(a*ans%c+b%c)%c+1;
    		ans=(k<=T.tree[T.root[x]].cnt)?T.query(T.root[x],1,d[0],k):T.tree[T.root[x]].sum;
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

P171. 黑客

[ARC107C] Shuffle Permutation

luogu P3239 [HNOI2015] 亚瑟王

CF455D Serega and Fun

BZOJ4026 dC Loves vmber Theory

  • 欧拉函数的计算式为 φ(n)=np|n,pPp1pn 的贡献统计前缀积后求逆元即可。

  • 难点在于怎么维护 p1p ,类似 luogu P1972 [SDOI2009] HH的项链 ,统计 [l,r] 中分解质因数后统计先前出现位置在 [0,l) 的统计答案即可。

  • 类似普通的判断素数,质因数分解时注意只筛到 n ,剩下的部分若是质数则单独计算。

    • 一个数 n 最多有一个质因子 n
    点击查看代码
    const ll p=1000777;
    ll prime[1000010],vis[1000010],a[1000010],mul[1000010],inv[1000010],inv_p[1000010],inv_pp[1000010],last[1000010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				break;
    			}
    		}
    	}
    }
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;;
    }
    struct PDS_SMT
    {
    	ll root[1000010],rt_sum=0;
    	struct SegmentTree
    	{
    		ll ls,rs,mul,inv;
    	}tree[80000<<5];
    	#define lson(rt) tree[rt].ls
    	#define rson(rt) tree[rt].rs
    	int build_rt()
    	{
    		rt_sum++;
    		return rt_sum;
    	}
    	void build_tree(ll &rt,ll l,ll r)
    	{
    		tree[rt].mul=1;
    		tree[rt].inv=1;
    		if(l==r)
    		{
    			return;
    		}
    		ll mid=(l+r)/2;
    		build_tree(lson(rt),l,mid);
    		build_tree(rson(rt),mid+1,r);
    	}
    	void update(ll pre,ll &rt,ll l,ll r,ll pos,ll id)
    	{
    		rt=build_rt();
    		tree[rt]=tree[pre];
    		tree[rt].mul=tree[pre].mul*inv_p[id]%p;
    		tree[rt].inv=tree[pre].inv*inv_pp[id]%p;
    		if(l==r)
    		{
    			return;
    		}
    		ll mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			update(lson(pre),lson(rt),l,mid,pos,id);
    		}
    		else
    		{
    			update(rson(pre),rson(rt),mid+1,r,pos,id);
    		}
    	}
    	ll query(ll rt1,ll rt2,ll l,ll r,ll x,ll y)
    	{
    		if(x<=l&&r<=y)
    		{
    			return tree[rt2].mul*tree[rt1].inv%p;
    		}
    		ll mid=(l+r)/2,ans=1;
    		if(x<=mid)
    		{
    			ans=ans*query(lson(rt1),lson(rt2),l,mid,x,y)%p;
    		}
    		if(y>mid)
    		{
    			ans=ans*query(rson(rt1),rson(rt2),mid+1,r,x,y)%p;
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    	ll n,q,l,r,ans=0,i,j;
    	cin>>n>>q;
    	isprime(1000000);
    	mul[0]=inv[0]=1;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		mul[i]=mul[i-1]*a[i]%p;
    		inv[i]=inv[i-1]*qpow(a[i],p-2,p)%p;
    	}
    	inv_p[0]=inv_pp[0]=1;
    	for(i=1;i<=len;i++)
    	{
    		inv_p[prime[i]]=(prime[i]-1)*qpow(prime[i],p-2,p)%p;
    		inv_pp[prime[i]]=prime[i]*qpow(prime[i]-1,p-2,p)%p;
    	}
    	T.build_tree(T.root[0],0,n);
    	for(i=1;i<=n;i++)
    	{
    		T.root[i]=T.root[i-1];
    		for(j=1;j<=len&&prime[j]*prime[j]<=a[i];j++)
    		{
    			if(a[i]%prime[j]==0)
    			{
    				while(a[i]%prime[j]==0)
    				{
    					a[i]/=prime[j];
    				}
    				T.update(T.root[i],T.root[i],0,n,last[prime[j]],prime[j]);
    				last[prime[j]]=i;
    			}
    		}
    		if(a[i]>1)//单独计算
    		{
    			T.update(T.root[i],T.root[i],0,n,last[a[i]],a[i]);
    			last[a[i]]=i;
    		}
    	}
    	for(i=1;i<=q;i++)
    	{
    		cin>>l>>r;
    		l^=ans;
    		r^=ans;
    		ans=mul[r]*inv[l-1]%p;
    		ans=ans*T.query(T.root[l-1],T.root[r],0,n,0,l-1)%p;
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
posted @   hzoi_Shadow  阅读(142)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
扩大
缩小
点击右上角即可分享
微信分享提示