做题简记
CF1023D Array Restoration
题解:写过了
CF1332B Composite Coloring
题意
给定长度为 \(n\) 的合数序列 \(a\),给该序列染色,使每种颜色的数的 \(\gcd>1\),选色的总色数小于 \(11\)(可证明一定有解),不必最小化染色的颜色数。
\(a_i \le 1000,n \le 1000\)
做法
看到求 \(\gcd\),显然可以想到对每个数进行分解质因数。每个数一定可以写成 \(p_1*p_2\) 的形式,其中 \(p_1\) 为一个小于 \(\sqrt a_i\) 的质数。打表发现,在 \(\sqrt {1000}\) 内的质数小于 \(11\) 个,因此就可以直接对每个数的最小质因子染同一种颜色,得到的一定是合法解。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+100;
int n,tot;
int a[N],ans[N];
int cnt;
int num[N],p[N];
bool f[N],vis[N];
void init()
{
for(int i=2;i<=100;i++)
{
if(f[i]) continue;
p[++cnt]=i;
for(int j=i+i;j<=100;j+=i) f[j]=true;
}
}
void work()
{
memset(vis,0,sizeof vis),memset(ans,0,sizeof ans),memset(num,0,sizeof num);
tot=0;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=cnt;j++)
if(a[i]%p[j]==0)
{
ans[i]=j;
break;
}
for(int i=1;i<=n;i++) vis[ans[i]]=true;
for(int i=1;i<=cnt;i++)
if(vis[i])
num[i]=++tot;
for(int i=1;i<=n;i++) ans[i]=num[ans[i]];
cout<<tot<<endl;
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
cout<<endl;
}
signed main()
{
int T;
cin>>T;
init();
while(T--) work();
return 0;
}
CF534D Handshakes
题意
现在有一个会场,有 \(n\) 人要依次进入。每一个人进场的时候会和场内所有人握手。先给出一个可能的握手序列,判断这个握手序列是否有解,如果有解,给出一个可能解。
\(n<=2e5\)
做法
考虑简化题意,就是要从 \(0\) 开始走每次 \(+1\) 并且有任意次 \(−3\),经过每个点一个确定的次数。
我们观察题意,发现显然可以排序,排序更好做并且对结果无影响。
发现直接连边建图会建出一张近完全图,无论是内存还是搜索的时间复杂度的无法通过,考虑其他性质。
考虑找一种一定可行的解。发现从小到大一定可以走到最大值,然后走到最大值之后一定会向后退,直到下一个可以走的地方,发现这个结论显然正确。
发现这是一个递归的过程,即可证明正确性。
直接递推就可以完成了。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=2e5+100;
int n;
vector<int> a[N];
int ans[N];
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
a[x].pb(i);
}
int idx=0,num=1;
while(num<=n)
{
while(idx>2 && !a[idx].size()) idx-=3;
if(a[idx].size()) ans[num++]=a[idx].back(),a[idx].pop_back(),idx++;
else return cout<<"Impossible"<<endl,0;
}
cout<<"Possible"<<endl;
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
return 0;
}
CF1691D Max GEQ Sum
题意
给定长度为 \(n\) 的数组 \(A\),判断 \(A\) 是否满足
$ \forall i,j \in [1,n] (i < j), \max(a_i,a_{i+1},a_{i+2}, …… ,a_j) \ge {\textstyle \sum_{k=i}^{j}} a_k $
做法
考虑直接去判断满足的条件很难,选择找到不满足的条件。
如果一个区间内最大值是负数则显然满足(最小值是正数显然不满足)。
显然最大值必须是正数,然后找到这个区间作为最大值的和最大的区间即可。
然后我们就显然发现可以对每一个正数进行判断。
每个正数可以做最大值的区间即为前一个比它大的数与后一个比他大的数所形成的区间。可以用单调栈找到这个区间,用单调栈可以完成这个事情,线段树保存前缀和即可。
继续考虑优化,发现单调栈中每一个元素都有:它向左/右延伸的最大和一定小于等于零(它一定是一个满足要求的元素)。因此,我们只需在弹栈的时候判断当前元素延伸到栈顶元素的这一段区间和是否大于零即可(如果把栈顶元素左/右的不在栈中的元素加进来值显然不会增大)。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+100;
int n;
int a[N];
int sumq[N],sumh[N];
struct NODE{
int val,location;
};
void work()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sumq[0]=0;
for(int i=1;i<=n;i++) sumq[i]=sumq[i-1]+a[i];
sumh[n+1]=0;
for(int i=n;i;i--) sumh[i]=sumh[i+1]+a[i];
bool sg=0;
stack<NODE> stc;
for(int i=1;i<=n;i++)
{
int maxx=0;
while(stc.size() && stc.top().val<=a[i])
maxx=max(maxx,sumq[i-1]-sumq[stc.top().location-1]),stc.pop();
if(maxx) return cout<<"NO"<<endl,void();
stc.push({a[i],i});
}
while(stc.size()) stc.pop();
for(int i=n;i;i--)
{
int maxx=0;
while(stc.size() && stc.top().val<=a[i])
maxx=max(maxx,sumh[i+1]-sumh[stc.top().location+1]),stc.pop();
if(maxx) return cout<<"NO"<<endl,void();
stc.push({a[i],i});
}
return cout<<"YES"<<endl,void();
}
signed main()
{
int T;
cin>>T;
while(T--) work();
return 0;
}
AT_arc194_a [ARC194A] Operations on a Stack
题意
给定一个长度为 \(n\) 的数组 \(A\) 和一个空栈,顺序遍历这个数组,对于每一个元素,可以选择弹栈(栈非空)或者入栈(不可以只选一个),求栈中元素可能最大值。
做法
观察序列,发现加入后如果要删除,那执行删数的位置和加入被删除数的位置一定在一起。如果是连续删除,发现加加删删与加删加删等价。所以所有删除操作一定可以表示为加删。
考虑不出现连续的删除操作。那么即删除这个数之前的数。相当于把这个序列中当前数和前一个数全部扔掉。
可以 \(DP\),\(f_i=\max(f_{i-1}+a_i,f_{i-2})\)。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+100;
int n;
int f[N],a[N];
signed main()
{
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
f[1]=a[1];
for(int i=2;i<=n;++i) f[i]=max(f[i-1]+a[i],f[i-2]);
return cout<<f[n]<<endl,0;
}
AT_arc194_b [ARC194B] Minimum Cost Sort
题意
给定一个排列,可交换相邻相邻两项,付出的代价为靠左一项的值。求排好序所需最小代价。
\(n<1e5\)
做法
这就是一个冒泡排序。
发现每次交换两个数一定会减小逆序对,证明显然。考虑对 \(n\) 进行归位处理。发现如果把 \(n\) 扔到左边一定会增加逆序对数,所以 \(n\) 一定会先移到最后。
发现可以把其化为子问题或者递归去求解。
考虑代价的计算。发现只需找到左边比当前数大的数即可。
树状数组维护即可。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define lowbit(x) (x&(-x))
#define x first
#define y second
using namespace std;
const int N = 2e5+5;
int n;
int p[N],qd[N],c[N];
pii ps[N];
int ask(int x)
{
int sum=0;
for(int i=x;i>0;i-=lowbit(i)) sum+=c[i];
return sum;
}
void modify(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=k;
}
signed main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
cin>>p[i];
qd[i]=ask(n)-ask(p[i]),modify(p[i],1);
ps[i]={p[i],i};
}
sort(ps+1,ps+n+1);
int ans=0;
for(int i=1;i<=n;i++)
{
int t=ps[i].y-qd[ps[i].y];
ans+=((t-1+i)*abs(t-i)>>1);
}
cout<<ans<<endl;
return 0;
}
AT_arc194_c [ARC194C] Cost to Flip
题意
给出两个长度为 \(n\) 的01数组,其中 \(A\) 串为当前数组,\(B\) 为目标数组。给出代价数组 \(C\)。
你每次可以进行以下操作(也可以不进行),使最终得到 \(A\) 与 \(B\) 相等:
-
选择一个数 \(i\),对 \(A_i\) 进行异或操作。
-
总代价加上 $ {\textstyle \sum_{i=1}^{n}} [A_i=1] C_i $。
求最小代价。
做法
通过观察,发现对于某一个 \(A_i\),存在四种情况。
-
若 \(A_i==0,B_i==0\) 则一定不需要对其进行操作(证明显然)。
-
若 \(A_i==1,B_i==0\) 则一定要尽快把其置为 \(0\),因为徒增代价显然是不利的。
-
若 \(A_i==0,B_i==1\) 则一定要把把 \(A_i\) 置为 \(1\) 的操作放在后面,因为过早增加代价显然也是不利的。
-
若 \(A_i==1,B_i==1\),则发现可能会出现两种情况,一种是不改变他,使其一直进行贡献,一种是把它拆成 \(10+01\)。
显然对这个序列可以进行排序,对结果无影响。
我们显然发现会更改的 \(11\) 一定为 \(11\) 中代价较大的部分。
发现 \(10\) 和 \(01\) 的顺序已经固定,考虑把加入 \(11\) 的操作看作向这个序列插入。
显然具有单调性。可以枚举插入的位置。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+100;
int n;
int a[N],b[N],c[N];
int A[N],idx_a,B[N],idx_b,C[N],idx_c;
int sum,res;
int s[N];
bool cmp(int x,int y)
{
return x>y;
}
struct BIT{
#define lowbit(x) (x&-x)
int tr[N];
void change(int x,int y)
{
for(int i=x;i<N;i+=lowbit(i)) tr[i]+=y;
}
int ask(int x)
{
int tot=0;
for(int i=x;i;i-=lowbit(i)) tot+=tr[i];
return tot;
}
}Sum,Cnt;
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<=n;i++)
{
if(a[i]==1 && b[i]==0) A[++idx_a]=c[i];
if(a[i]==0 && b[i]==1) B[++idx_b]=c[i];
if(a[i]==1 && b[i]==1) C[++idx_c]=c[i];
}
sort(A+1,A+idx_a+1,cmp),sort(B+1,B+idx_b+1),sort(C+1,C+idx_c+1,cmp);
for(int i=1;i<=idx_a;i++) sum+=A[i]*(i-1);
for(int i=1;i<=idx_b;i++) sum+=B[i]*(idx_b-i+1);
for(int i=1;i<=idx_c;i++) sum+=C[i]*(idx_a+idx_b);
for(int i=idx_c;i;i--) s[i]=s[i+1]+C[i];
res=sum;
for(int i=1;i<=idx_a;i++)
Sum.change(A[i],A[i]),Cnt.change(A[i],1);
for(int i=1;i<=idx_b;i++)
Sum.change(B[i],B[i]),Cnt.change(B[i],1);
for(int i=1;i<=idx_c;i++)
{
sum+=Sum.ask(C[i]);
sum+=(idx_a+idx_b+2*(i+1)-Cnt.ask(C[i])-1)*C[i];
sum-=(idx_a+idx_b+2*i)*C[i];
sum+=s[i+1]*2;
res=min(res,sum);
Sum.change(C[i],C[i]),Cnt.change(C[i],1);
}
cout<<res<<endl;
return 0;
}
CF1703G Good Key, Bad Key
题意
你有 \(n\) 个宝箱。现在你要依次开启这些宝箱。对于一个宝箱,你可以选择花费 \(k\) 的代价开启,或者不花费代价开启,但是从这个宝箱开始,之后(包括当前)的宝箱价值减半,该效果会叠加。
求最大价值。
\(n \le 1e5,a_i \le 1e9\)
做法
显然发现 \(\log {1e9} \le 30\),所以我们可以很显然得想到不花费代价的次数一定在三十次之内,因为超过三十次就一定让剩下的箱子价值全部归为 \(0\)。
所以最多减少 \(\log V\) 次,考虑DP,记录当前减少次数和开启到了第 \(k\) 的宝箱即可。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define MA(a,b) a=max(a,b)
using namespace std;
const int N=1e5+5,INF=1e18;
int n,k,ans;
int a[N][35];
int f[N][35];
void work()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
for(int j=0;j<=30;j++)
f[i][j]=-INF;
for(int i=1;i<=n;i++) cin>>a[i][0];
for(int i=1;i<=n;i++)
for(int j=1;j<=30;j++)
a[i][j]=a[i][j-1]/2;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=min(i,30);j++)
{
MA(f[i][j],f[i-1][j]+a[i][j]-k);
if(j-1>=0)
MA(f[i][j],f[i-1][j-1]+a[i][j]);
}
if(i>=31)
MA(f[i][30],f[i-1][30]);
}
ans=-INF;
for(int j=0;j<=30;j++)
ans=max(ans,f[n][j]);
cout<<ans<<endl;
}
signed main()
{
int T;
cin>>T;
while(T--) work();
return 0;
}
AT_abc154_f [ABC154F] Many Many Paths
题意
求从点 \((0,0)\) 走到矩阵 \(([r1,r2],[c1,c2])\) 内任意一点的方案数,只能向上或向下走。
做法
考虑从 \((0,0)\) 走到点 \((r,c)\) 的方案数,发现答案是 $\binom{r+c}{c} $。
所以总方案数为 $ {\textstyle \sum_{i=r1}^{r2}} {\textstyle \sum_{j=c1}^{c2}} \binom{i+j}{j} $。
显然发现内层其实是一个上指标求和($\sum_{n}^{l=0} \binom{l}{k} = \binom{n+1}{k+1} $)。
然后就可以求了。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int, int>;
const int mod = 1000000007,N=2e6+100;
int r1,c1,r2,c2;
int fac[N];
int inv[N],invfac[N];
int calc(int r, int c)
{
return ((1ll*fac[r+c+2]*invfac[r+1]%mod*invfac[c+1]%mod)+mod-1)%mod;
}
signed main()
{
cin>>r1>>c1>>r2>>c2;
fac[0]=1;invfac[0]=1,inv[0]=1,inv[1]=1;
for(int i=1;i<=r2+c2+2;i++)
{
if(i != 1) inv[i] = 1ll * inv[mod % i] * (mod - mod/i) % mod;
fac[i] = 1ll * fac[i-1] * i % mod;
invfac[i] = 1ll * invfac[i-1] * inv[i] % mod;
}
cout << (4ll*mod + calc(r2, c2) - calc(r1-1, c2) - calc(r2, c1-1) + calc(r1-1, c1-1)) % mod << endl;
}
AT_arc160_c [ARC160C] Power Up
题意
给定一个可重集,两个相同的数 \(x\) 可以合并为 \(x+1\),求最终可能得到的不同的集合的数量。
做法
发现如果两个操作方案是不同的,则对应得到的集合不同。
考虑从小到大 \(DP\)。考虑枚举当前这个值的合并次数,转移下一个值,记录合并上来的数量。
\(f(x,y)=\sum f(x+1,i)\)。
前缀和优化。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+100,mod=998244353;
int n;
int a[N],f[N];
int s[N];
int maxx,minn=1e18;
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
a[x]++,maxx=max(maxx,x),minn=min(minn,x);
}
int t=a[minn];
for(int i=t;i>=0;i--) s[i]=1;
for(int i=minn+1;i<=maxx+50;i++)
{
t=a[i]+t/2;
for(int j=0;j<a[i];j++) f[j]=0;
for(int j=a[i];j<=t;j++) f[j]=s[(j-a[i])*2];
s[t]=f[t];
for(int j=t-1;j>=0;j--) s[j]=(s[j+1]+f[j])%mod;
}
cout<<s[0]<<endl;
return 0;
}
AT_abc288_f [ABC288F] Integer Division
题意
给定一个 \(n\) 位整数 \(x\),你可以把 \(x\) 分成若干段,每一种分发权值为每一段相乘乘积。求所有分法权值之和,答案对 \(998244353\) 取模。
\(n\le2e5\)
做法
显然考虑 \(DP\),令 \(f_i\) 为考虑到 \(x\) 第 \(i\) 位的分法的和。
令 \(S(i,j)\) 表示 \(x\) 的第 \(i\) 到 \(j\) 位组成的数字。
则显然有:$f_i= {\textstyle \sum_{j=0}^{i-1}}f_j*s(j+1,i) $。
考虑前缀和优化,发现 \(s(j+1,i)=s(j+1,i-1)*10+x_i\).
所以 $f_i=\sum_{j=0}^{j-1} f_j \times s(j+1,i-1) \times 10+ \sum_{j=0}^{i-1} f_j \times x_i $ 。
因为 \(j=i-1\) 时,不存在 \(s(j+1,i-1)==s(i,i-1)\)。
显然,有 \(\sum_{j=0}^{i-1} f_j \times s(j+1, i-1) \times 10 = \sum_{j=0}^{i-2} f_j \times s(j+1, i-1) \times 10 = f_{i-1} \times 10\)。
故:\(f_i=f_{i-1}*10+\sum_{j=0}^{i-1} f_j*x_i\)。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+100,mod=998244353;
int n;
char a[N];
int f[N],g[N];
signed main()
{
cin>>n>>a+1;
f[1]=a[1]-'0',g[1]=1;
for(int i=2;i<=n;i++)
{
f[i]=(f[i-1]*10+g[i-1]*(a[i]-'0'))%mod;
g[i]=g[i-1]+f[i-1];
f[i]=(f[i]+f[i-1]*(a[i]-'0'))%mod;
}
cout<<f[n]<<endl;
return 0;
}
AT_abc288_d [ABC288D] Range Add Query
题意
\(q\) 次询问区间 \([l_i,r_i]\) 能否通过任意次长度为 \(k\) 的区间加变为全 \(0\)。
\(n,q \le 1e5\)
做法
显然可以想到把题目转化为差分数组来做。
发现直接用差分数组去实现满足不了这个时间复杂度,考虑前缀和优化。
如何前缀和?
考虑每次修改实质上是把其前面的数放到其后面第 \(k\) 个位置上。我们想知道其是否为 \(0\),只需要统计其前缀和是否为 \(0\)。
记得特判第一组和最后一组。
CODE
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,k;
int a[N],s[N][10];
void work()
{
int l,r;
cin>>l>>r;
for(int i=max(r-k+2,l);i<=r;i++)
if(i%k==l%k&&s[i][i%k]-s[l-1][i%k]+a[l-1]!=0)
return cout<<"No"<<endl,void();
else if(i%k!=l%k&&s[i][i%k]-s[l-1][i%k]!=0)
return cout<<"No"<<endl,void();
return cout<<"Yes"<<endl,void();
}
signed main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
for(int j=0;j<k;j++) s[i][j]=s[i-1][j];
s[i][i%k]+=a[i]-a[i-1];
}
int T;
cin>>T;
while(T--) work();
return 0;
}