A

模拟不谈

void solve(void){
	cin>>n;
	char c;
	cin>>c;
	if(c=='*')cout<<1<<' '<<n;
	else if(c=='+')cout<<n/2<<' '<<(n+1)/2;
	else cout<<n+1<<' '<<1;
}

B

贪心,n个位置,k个东西把他们分成一些区间,每个长度不小于t的区间个数最大值?

首先k个东西要用来隔开,那么剩余n-k个东西可以划分区间,t个一组,那么组数最大值就是

但是交上去发现wa了,实际上这是只考虑n-k个东西,能组成几组,实际上分组的时候,每两组之间还需要隔开,用那k个东西来隔,那显然k个东西最多分出k+1段,因此答案是

void solve(void){
	cin>>n>>m>>k;
	int ans=(n-k)/m;
	cout<<min(ans,k+1)<<'\n';
}

C

位运算,贪心

我们可以交换位,也可以直接反转位,问得到的最小代价?按位来看的话,都是不合法的,需要操作的位,然后一个重要的观察是,两个不合法的位,我们交换的这两位,都可以变成合法,也就是交换只需要一次操作就能改好两位,而使用反转需要操作两次,因此除非交换一次的代价比反转两次还大,我们应该优先考虑使用交换。

那么优先考虑交换的话,两个不合法位可以一次交换操作解决,问最小代价就是问最小对数,这是经典贪心,如果不同类的错误位可以配对,那么最多的那类错误不超过一半,直接两两配对即可,如果不合法位数是奇数,还会剩下一位,用反转操作即可,如果超过一半,多出来的用反转。

如果交换一次代价比反转两次还大,显然应该全用反转,把全用反转的代价和前面优先交换的代价取即可

void solve(){
	cin>>n;
	int x,y;
	cin>>x>>y;
	string a,b,c;
	cin>>a>>b>>c;
	vector<int>v(4);
	int cnt=0;
	rep(i,0,n-1){
		int p=a[i]-'0',q=b[i]-'0';
		if((p^q)!=c[i]-'0'){
			if(p==0&&q==0)v[0]++;
			if(p==0&&q==1)v[1]++;
			if(p==1&&q==0)v[2]++;
			if(p==1&&q==1)v[3]++;
			cnt++;
		}
	}
	sort(v.begin(),v.end());
	int sum=v[1]+v[0]+v[2]+v[3];
	int ans;
	if(v[3]>sum-v[3]){
		ans=x*(v[3]-sum)+y*sum;
	}
	else{
		ans=(sum%2)*x+(sum-(sum%2))/2*y;
	}
	cout<<min(ans,x*cnt)<<'\n';
}

D

前缀和,调和级数,思维

把01串划分成k段长度相等的,每一段可以全部反转,随意排列,最后的01段数作为答案,求的答案

由于我们能随意排列,可以把每一段里颜色相同的都集中起来,也就是分成两部分,然后看前一段结尾的颜色,如果是0,我们就把这一段的0移到开头,1的话同理,这样我们一段里,会产生的01分界点就只有1个。如果这一段是全0或者全1,如果前一段结尾是1我们就把他变成全1,结尾是0就把他变成全0,不会产生01分界点。

所以可以看出答案就是k段里,颜色不全相同的段数,我们可以前缀和统计0的个数,然后枚举每一段,统计颜色不全相同的段数。

对于多个的值,我们就对每个按上面的策略来就行了,这相当于调和级数枚举,复杂度,没问题的

void solve(){
	cin>>n;
	string s;
	cin>>s;
	s=' '+s;
	vi pre(n+1);
	rep(i,1,n){
		pre[i]=pre[i-1]+(s[i]=='0');
	}
	int ans=0;
	rep(i,1,n){
		int cur=1;
		for(int j=i;j<=n;j+=i){
			int c=pre[j]-pre[j-i];
			if(c==0||c==i){
				continue;
			}
			else{
				cur++;
			}
			if(j+i>n){
				int c=pre[n]-pre[j];
				if(c==0||c==n-j)continue;
				else cur++;
			}
		}
		//cout<<cur<<' ';
		ans^=cur;
	}
	cout<<ans<<'\n';
}

E

大模拟/分类讨论特判

很屎山,不贴代码了,大概思路分两种,一个是dfs预处理出来所有局面的输赢,然后来一个询问就去查表;另一个是其实情况不多,的五子棋很容易就会分出胜负,可以直接对局面分类讨论,然后特判谁会赢

F

分治,概率dp

先不说考虑多个询问,对于一个给定区间,每个位置获得的概率是,要求最终获得东西的个数模的概率,这就是个类似01背包的问题,就是带上概率,以及背包值域是取模,也就是循环的,这个dp很好写。

那么如果有多个询问呢?这里一种重要的思想就是分治处理,当前区间的话,只在这层处理跨越中点的询问,全部在左侧或右侧的询问留到下一层处理,具体处理就是我们对分别跑上述dp,然后跨越的询问,我们用两个dp的结果可以拼出来,比如,就是说考虑两个区间,左边获得个,右边获得个,考虑到取模应该是右边获得个,我们就枚举,然后查左右两个dp数组的值,拼起来就行了。

这样每一层dp的复杂度是的,递归方程就是,总体复杂度,然后处理询问的部分,个询问,每个询问都要枚举,复杂度是,这里,是可以做的

void solve(){
	cin>>n>>m>>k;
	vector<mint>a(n+1),b(n+1);
	rep(i,1,n)cin>>a[i];
	rep(i,1,n){
		a[i]=mint(1)/a[i];
		b[i]=mint(1)-a[i];
	}
	vi qry,l(m+1),r(m+1),p(m+1);
	rep(i,1,m){
		cin>>l[i]>>r[i]>>p[i];
		qry.push_back(i);
	}
	vector<mint> ans(m+1);
	vector<array<mint,15>>f(n+1),g(n+1);
	auto &&cal=[&](auto &&cal,int x,int y,vi &qry){
		if(qry.empty())	return;
		if(x==y){
			for(int &id:qry){
				if(p[id]==0)ans[id]=b[x];
				else if(p[id]==1)ans[id]=a[x];
				else ans[id]=0;
			}
			return;
		}
		
		int mid=(x+y)/2;
		f[mid][0]=b[mid],f[mid][1]=a[mid];
		rep(i,2,k-1){
			f[mid][i]=0;
		}
		rep1(i,mid-1,x){
			rep(j,0,k-1){
				f[i][j]=0;
			}
			rep(j,0,k-1){
				f[i][(j+1)%k]+=f[i+1][j]*a[i];
				f[i][j]+=f[i+1][j]*b[i];
			}
		}
		g[mid+1][0]=b[mid+1],g[mid+1][1]=a[mid+1];
		rep(i,2,k-1){
			g[mid+1][i]=0;
		}
		rep(i,mid+2,y){
			rep(j,0,k-1){
				g[i][j]=0;
			}
			rep(j,0,k-1){
				g[i][(j+1)%k]+=g[i-1][j]*a[i];
				g[i][j]+=g[i-1][j]*b[i];
			}
		}
		
		vi lqry,rqry;
		for(int &id:qry){
			if(r[id]<=mid){
				lqry.push_back(id);
			}
			else if(l[id]>=mid+1){
				rqry.push_back(id);
			}
			else{
				rep(i,0,k-1){
					int lcnt=i,rcnt=(p[id]-i+k)%k;
					ans[id]+=f[l[id]][lcnt]*g[r[id]][rcnt];
				}
			}
		}
		cal(cal,x,mid,lqry);
		cal(cal,mid+1,y,rqry);
	};
	cal(cal,1,n,qry);
	rep(i,1,m){
		cout<<ans[i]<<'\n';
	}
}

G

虚树,树剖

要找一棵树上两个颜色相同的点,一个颜色比他们大的点,且在前两个点路径上,这样的三元组的个数,并且两个颜色相同的点的相同组合只会被计算一次。

首先颜色相同这一点可以让我们想到虚树,我们按颜色建立虚树,边权设置为两点间,颜色更大的点的个数,然后可以在虚树上dp解决这个问题。

计算边权,需要知道树上一条路径上大于某个值的点数,这可以主席树,但是太麻烦了,离线+树剖简单一点。具体来说就是我们按颜色从大到小建虚树,每处理完一个颜色就把这个颜色的所有点加入树剖得线段树,那么我们每次建树时,所有颜色更大的点就都在线段树里了,我们直接树剖查询路径和就行

然后dp的时候贡献有点麻烦,边权,也就是被压缩成一条边的点可以有贡献,虚树的节点由于可能不是当前颜色的,也可能有贡献,我们分开计算。当前在,枚举到儿子,那么所在子树的所有关键点,都可以通过路径上的颜色更大的点,和子树之外的所有关键点组成答案,这是边权的贡献。同时如果的颜色大于当前枚举的颜色,也可以通过和其它点组成答案。

这样做的话我们边权就可以定义为之间的点中,不含,颜色大于当前颜色的点数,比较好讨论。

struct Tree{
	#define ls u<<1
	#define rs u<<1|1
    struct Node{
        int l,r;
        ll mx,add;
    }tr[N<<2];
     
    void pushup(int u){
        tr[u].mx=tr[ls].mx+tr[rs].mx;
    }
     
    void pushdown(int u){
        if(tr[u].add){
            tr[ls].mx+=tr[u].add*(tr[ls].r-tr[ls].l+1);
            tr[rs].mx+=tr[u].add*(tr[rs].r-tr[rs].l+1);
            tr[ls].add+=tr[u].add;
            tr[rs].add+=tr[u].add;
            tr[u].add=0;
        }
    }
     
    void build(int u,int l,int r){
        tr[u]={l,r,0,0};
        if(l==r){
            tr[u].mx=0;
            return;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid);    build(rs,mid+1,r);
        pushup(u);
    }
     
    void modify(int u,int l,int r,int val){
        if(tr[u].l>=l&&tr[u].r<=r){   
            tr[u].mx+=val*(tr[u].r-tr[u].l+1);
            tr[u].add+=val;
            return ;
        }
        else{
            int mid=(tr[u].l+tr[u].r)>>1;
            pushdown(u);
            if(mid>=l)   modify(ls,l,r,val);
            if(r>mid) modify(rs,l,r,val);
            pushup(u);  
        }
    }
     
    ll query(int u,int l,int r){
        if(l<=tr[u].l&&tr[u].r<=r)    return  tr[u].mx;
        pushdown(u);
        int mid=(tr[u].l+tr[u].r)>>1;
        if(r<=mid)return query(ls,l,r);
        if(l>mid)return query(rs,l,r);
        return query(ls,l,r)+query(rs,l, r);
    }
}t;
void solve(){
	cin>>n;
	t.build(1,1,n);
	vvi g(n+10);
	vector<array<int,30>> f(n+10);
	vi lg(n+10),d(n+10),dp(n+10),w(n+1),sz1(n+1);
	rep(i,1,n)cin>>w[i];
	rep(i,2,n)lg[i]=lg[i>>1]+1;
	
	auto &&dfs=[&](auto &&dfs,int now,int fa)->void{
	    f[now][0]=fa;
	    rep(i,1,lg[d[now]]){
	        f[now][i]=f[f[now][i-1]][i-1];
	    }
	    for(int v:g[now]){
	        if(v==fa)continue;
	        d[v]=d[now]+1;
	        dfs(dfs,v,now);
	    }
	};
	
	auto lca=[&](int x,int y)->int{
	    if(d[x]<d[y])swap(x,y);
	    while(d[x]>d[y]){
	        x=f[x][lg[d[x]-d[y]]];
	    }
	    if(x==y)return x;
	    for(int i=lg[d[x]];i>=0;i--)
	        if(f[x][i]!=f[y][i]){
	            x=f[x][i],y=f[y][i];
	        }
	    return f[x][0];
	};
	rep(i,1,n-1){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(dfs,1,-1);
	
	vi dep(n+1),fa(n+1),sz(n+1,1),son(n+1);
	auto dfs1=[&](auto &&dfs1,int u,int f)->void{
		int mx=0,mxson=0;
		for(int v:g[u]){
			if(v==f)continue;
			dep[v]=dep[u]+1;
			fa[v]=u;
			dfs1(dfs1,v,u);
			sz[u]+=sz[v];
			if(sz[v]>mx){
				mx=sz[v];
				mxson=v;
			}
		}
		son[u]=mxson;
	};
	dfs1(dfs1,1,-1);
	int cnt=0;
	vi top(n+1),dfn(n+1);	
	auto &&dfs2=[&](auto &&dfs2,int u,int topf)->void{
		dfn[u]=++cnt;
		top[u]=topf;
		if(!son[u])return;
		dfs2(dfs2,son[u],topf);
		for(int v:g[u]){
			if(v==fa[u]||v==son[u])continue;
			dfs2(dfs2,v,v);
		}
	};
	dfs2(dfs2,1,1);
	auto ask_chain=[&](int x,int y)->int{
		int res=0;
		while(top[x]!=top[y]){
			if(dep[top[x]]<dep[top[y]])swap(x,y);
			res+=t.query(1,dfn[top[x]],dfn[x]);
			x=fa[top[x]];
		}
		if(dep[x]>dep[y])swap(x,y);
		res+=t.query(1,dfn[x],dfn[y]);
		return res;
	};
	
	map<int,vi> bin;
	rep(i,1,n){
		bin[w[i]].push_back(i);
	}
	int ans=0;
	for(auto it=bin.rbegin();it!=bin.rend();it++){
		vi key=it->second;
		int curw=it->first;
		int tot=key.size();
		
		sort(key.begin(),key.end(),[&](int x,int y){
			return dfn[x]<dfn[y];
		});
		vi all;
		all.push_back(key[0]);
		rep(i,1,tot-1){
			all.push_back(key[i]);
			all.push_back(lca(key[i],key[i-1]));
		}
		sort(all.begin(),all.end(),[&](int x,int y){
			return dfn[x]<dfn[y];
		});
		all.erase(unique(all.begin(),all.end()),all.end());
		int len=all.size();
		map<int,vi>g1;
		rep(j,0,len-2){
			int lc=lca(all[j],all[j+1]);
			g1[lc].push_back(all[j+1]);
		}
		
		auto &&dfs4=[&](auto &&dfs4,int u,int f)->void{
			sz1[u]=(w[u]==curw);
			for(int v:g1[u]){
				if(v==f)continue;
				dfs4(dfs4,v,u);
				int res=ask_chain(u,fa[v])-(w[u]>curw);//树剖
				if(w[u]>curw)ans+=sz1[u]*sz1[v];
				ans+=res*sz1[v]*(tot-sz1[v]);
				sz1[u]+=sz1[v];
			}
			if(w[u]>curw)ans+=sz1[u]*(tot-sz1[u]);
		};
		dfs4(dfs4,all[0],-1);
		
		for(int x:key){
			t.modify(1,dfn[x],dfn[x],1);
		}
	}
	cout<<ans;
}

H

贡献法,组合数学

把一个数组分成k段的权值,每一段的权值就是这一段的最大值×最小值,一个划分的权值就是k段的权值之和,问所有划分方案的权值和?

,加上划分k段,导致我开始以为这题能划分型dp做,结果写了半天发现转移方程是这样的 其中

这个转移方程根本没法优化,只能,于是我在尝试优化了半天之后红温了。

但其实这种有一堆方案,每个方案有个权值,求所有方案的权值和,还有另一种思路,就是贡献法:找到组成方案的基本元素,然后看这个元素能出现在多少种方案里,就能算出来这个元素对答案的贡献。

这样做就简单多了,这里基本单位显然是一段,我们直接枚举所有段,计算每一段的,然后看这一段能被包含在多少个划分方案里,划分过程等价于个空隙,划分次,这里我们确定了一段,这一段内部的就都不能划分了,边界相邻的位置也不能划分了,那么分讨一下这一段是不是开头或结尾,如果不是,那么相邻段有两个,剩下可划分的段只有了,并且已经有三段了,我们要让他变成段,还需要划分次,那么方案数就是,剩下靠边的情况特判一下就行

void solve(){
	cin>>n>>k;
	vi a(n+1);
	rep(i,1,n)cin>>a[i];
	if(k==1){
		int mx=0,mn=1e9;
		rep(i,1,n){
			mx=max(mx,a[i]);
			mn=min(mn,a[i]);
		}
		cout<<mx*mn%M2<<'\n';
		return;
	}
	int ans=0;
	rep(l,1,n){
		int mx=0,mn=1e9;
		rep(r,l,n){
			mx=max(mx,a[r]);
			mn=min(mn,a[r]);
			int cnt;
			if(l==1&&r==n){
				cnt=0;
			}
			else if(l==1){
				cnt=C(n-r-1,k-2);
			}
			else if(r==n){
				cnt=C(l-2,k-2);
			}
			else{
				cnt=C(n-1-(r-l+2),k-3);
			}
			ans+=mx*mn%M2*cnt%M2;
			ans%=M2;
		}
	}
	cout<<ans<<'\n';
}

I

guess

可以把一个数字乘2,或开方向下取整,问能不能把n变成m。手玩可以发现似乎我们可以用这个操作把一个数变成除0的任意数,当然这个数开始是0的话也没法变,那么只要nm有一个为0就无解,否则一定可以变

void solve(void){
	cin>>n>>m;
	if(n==0&&m==0)cout<<"Yes\n";
	else if(n==0||m==0)cout<<"No\n";
	else cout<<"Yes\n";
}

J

模拟

可以踩刹车,油门,离合,在每一秒的开始一瞬间操作,给初速度,问总路程?显然我们模拟每一秒的速度就行了

void solve(void){
	cin>>n;
	string s;
	cin>>s;
	int ans=0,v=0;
	for(char c:s){
		if(c=='0')v+=10,ans+=v;
		else if(c=='1')v=max(0ll,v-5),ans+=v;
		else ans+=max(0ll,v-10);
	}
	cout<<ans;
}

K

诈骗,看似计算几何,实则数论

给一堆圆,圆心和半径都是整数,问有多少个圆心,位于其它圆上?

注意到所有点和半径都是整数,那么一个点在另一个圆上,他和圆心,半径实际上构成了一个勾股三角形,那么如果我们能预处理出来所有勾股数,然后枚举圆心,计算这个圆心和所有勾股数能组合出的所有圆上整点,然后看这些点的出现次数就行了

所以关键是预处理勾股数。首先只需要处理出两两互质的勾股数,因为不互质的可以同除gcd变成两两互质的勾股数。然后就是怎么求,可以先分析一下

到这里出现相乘了,一般就可以枚举因子了。那么我们可以枚举,因为不大,那么范围内的互质勾股数并不多,我们就暴力枚举+剪枝就行,剪枝就是当我们发现任意一个大于最大的了就剪掉。

那么有

这就是一组互质勾股数了,我们找到一组,就相当于找到了这组的所有倍数,枚举倍数加入统计即可。

然后统计还有问题就是我们需要都是奇数,并且互质,不符合的直接剪掉

最后找到所有勾股数之后,我们枚举圆心,然后看这个半径出现在多少勾股数中,对于每一组勾股数在这个圆上有四个整点,我们把这四个整点的出现次数加入答案即可。当然显然的圆心上下左右距离的四个位置也是圆上整点,也要统计。

这里我选择把一个坐标压成存在

void solve(){
	cin>>n;
	int mx=4e5;
	unordered_map<int,vector<array<int,2>>>mp;
	unordered_map<int,int>cnt;
	vi x(n+1),y(n+1),r(n+1);
	for(int i=1;i<=mx;i+=2){
		for(int j=i+2;j<=mx;j+=2){
			int a=i*j,b=(j*j-i*i)/2,c=(j*j+i*i)/2;
			if(a>mx||b>mx||c>mx)break;
			if(__gcd(i,j)!=1)continue;
			rep(k,1,mx/c){
				mp[c*k].push_back({a*k,b*k});
			}
		}
	}
	rep(i,1,n){
		cin>>x[i]>>y[i]>>r[i];
		cnt[(x[i]<<30)+y[i]]++;
	}	
	int ans=0;
	rep(i,1,n){
		if(r[i]>mx)continue;
		ans+=cnt.count((x[i]<<30)+y[i]+r[i]);
		ans+=cnt.count((x[i]<<30)+y[i]-r[i]);
		ans+=cnt.count(((x[i]+r[i])<<30)+y[i]);
		ans+=cnt.count(((x[i]-r[i])<<30)-y[i]);
		
		for(auto &p:mp[r[i]]){
			int a=p[0],b=p[1];
			ans+=cnt.count(((x[i]+a)<<30)+y[i]+b);
			ans+=cnt.count(((x[i]-a)<<30)+y[i]+b);
			ans+=cnt.count(((x[i]+a)<<30)+y[i]-b);
			ans+=cnt.count(((x[i]-a)<<30)+y[i]-b);
			
			swap(a,b);
			ans+=cnt.count(((x[i]+a)<<30)+y[i]+b);
			ans+=cnt.count(((x[i]-a)<<30)+y[i]+b);
			ans+=cnt.count(((x[i]+a)<<30)+y[i]-b);
			ans+=cnt.count(((x[i]-a)<<30)+y[i]-b);
		}
	}
	cout<<ans<<'\n';
}

L

构造,数论。

1-n的数字最多用一次,问能找到多少三元组,满足恰好有两对元素互质,输出方案。

首先数论一个重要的结论是一定和是互质的,那么我们肯定要尝试一下连续的数一组,比如,这样有三组互质,不合法,那我们玩一下可以发现,改成就两组都合法了,而且这个模式可以无限延展,因为每6个数里一定有2个3的倍数,3个2的倍数,永远可以这么分配。

剩下如果小于3个,肯定不行,如果大于等于3个但是不到6个呢?似乎可以再分一组出来,但是如果剩下只有个,不存在,用不了,这一组还能成吗?

其实可以,只要就可以了,用上一组的来当最后一组的第二个偶数,把最后一组的拿去上一组当第二个的倍数

void solve(){
	cin>>n;
	if(n<=3)cout<<0<<'\n';
	else{
		cout<<n/3<<'\n';
		if(n%3==0&&(n/3)%2==1){
			int cur=1;
			rep(i,1,n/3-1){
				if(i%2)cout<<cur<<' '<<cur+1<<' '<<cur+3<<'\n';
				else{
					if(i!=n/3-1)cout<<cur+2<<' '<<cur+4<<' '<<cur+5<<'\n';
					else cout<<cur+2<<' '<<cur+4<<' '<<cur+8<<'\n';
					cur+=6;
				}

			}
			cout<<cur<<' '<<cur+1<<' '<<cur-1<<'\n';
		}
		else{
			int cur=1;
			rep(i,1,n/3){
				if(i%2)cout<<cur<<' '<<cur+1<<' '<<cur+3<<'\n';
				else cout<<cur+2<<' '<<cur+4<<' '<<cur+5<<'\n',cur+=6;			
			}
		}
	}
}