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