CF/AT做题记录
我发现三级标题有点小。
CF1905C
考虑先找到原串中的字典序最大串,这个直接单调栈求出。然后我们对这个字串 执行操作, 是不上升的所以肯定能排好序,我们在找 的时候顺便记录下 在原串中的位置,然后把排好序的 放回去判断,如果是不下降的则输出排好 所需的次数,正确性手玩得到。
CF1905D
先求出原排列的前缀 mex 然后考虑循环移位后产生的影响以及怎么维护这个序列。手模,向左移位时将所有大于 的前缀 mex 变成 ,注意最后一个 mex 一定是 ,操作过程珂以用一个 deque 维护,但是直接维护是 的,观察到在移位过程中会有很多连续相同的 mex,考虑记录下每个 mex 的个数把他们缩在一起,具体而言在 deque 里存一个二元组 表示有 个 ,每次弹出掉队首,对于队尾 的元素暴力全部弹出,并插入一个 表示 个 ,再插入一个 ,复杂度均摊 。注意实现细节。
点击查看代码
for(int i=1;i<=n;++i){
xx x=(xx){a[i],0};
sum-=q.front().val,q.front().cnt--;
if(!q.front().cnt) q.pop_front();
while(!q.empty()&&q.back().val>=a[i]){
sum-=q.back().val*q.back().cnt;
x.cnt+=q.back().cnt;
q.pop_back();
}
q.push_back(x);
q.push_back((xx){n,1});
sum+=x.val*x.cnt+n;
ans=max(ans,sum);
}
CF1913D
VP做不出来,有一个奇怪的 的模拟+组合数学想法,不会实现,结果是dp。看到求方案数并且元素之间的答案好像能转移,考虑dp,套路地设 表示只看前 个数并保留 时的方案数。考虑找到 前面第一个小于 的数,要满足 的所有元素都大于 或 ,即区间 的最小值是 或 。, 表示 的区间和。直接枚举找 是 ,考虑寻找 的一步用单调栈优化,并且记录前缀和,复杂度 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3*114514,M=1919810,mod=998244353,inf=1145141919810;
ll T;
ll n,a[N],dp[N],sum[N],s[N];
//前i个不删i时的方案数
void solve(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],dp[i]=sum[i]=s[i]=0;
dp[0]=1,sum[1]=1; a[0]=inf;
ll t=0,minn=inf,res=0,ans=0;
for(int i=1;i<=n;++i){
while(t&&a[s[t]]>a[i]) res=(res-dp[s[t]]+mod)%mod,--t;
dp[i]=(sum[i-1]-sum[s[t]]+mod)%mod;
if(!t) ++dp[i],dp[i]%=mod;
dp[i]+=res,dp[i]%=mod;
res+=dp[i],res%=mod;
s[++t]=i; sum[i]=(sum[i-1]+dp[i])%mod;
}
for(int i=n;i>=1;--i)
if(a[i]<minn)
minn=a[i],ans=(ans+dp[i])%mod;
cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>T;
while(T--) solve();
return 0;
}
CF1909B
这么简单做法都想到了还没做出来,烦。你手玩一下就能发现答案只会是 的幂次,直接模拟再用个 set 就没了,一开始还在想什么奇偶性。
CF1909C
一个简单的贪心思路,给我点时间也想得出来。先对 从大到小排序,然后把 放到 set 里,依次对每个 找到大于等于它的第一个 ,配起来放进 vector 里然后从 set 里删掉那个 ,再把得到的值从小到大排序, 从大到小排,就做完了。
CF1909D
ABC128D
挺简单一个题,但是脑子抽了。你直接枚举从左右开始能取到的地方,然后把取到的数排个序,在剩余次数内尽量把负数减掉就没了。从右向左的循环别写成 ++i
ABC130E
dp,初始把所有空串赋为 ,然后每个 都能由 转移来,注意重复算了一个 要减掉。然后如果 那么前面的每一个子序列都能再加一,也就是总共再加一个 。
ABC132D
组合数,把 个球分成 段就是个插板法,方案数 ,然后将这 段插入到长度 的序列中,有 个位置,方案数为 ,注意取模乘逆元。
ABC141E
字符串,直接枚举 ,考虑二分答案优化,我们记录每个区间长度对应的 base 并且做一个哈希值的前缀和,求哈希值就 a[r]-a[l-1]*base[r-l+1]
,其中 是区间左端点 是长度,还有哈希都用双哈希,不写要遭。
CF1918D
二分答案是能想到的,check的单调队列维护dp就不会了。我们设 为以 为一段数的结尾时的总值。
他妈的不想写了,下次还填不简单。
ABC339G
强制在线,能想到用树套树或分块解决,但我不会树套树,于是考虑分块。散块直接暴力枚举一遍就完了。对于整块,我们考虑先分别对每个块内的元素排序,在每个块中记录前缀和,这样查询时对每个整块就可以先二分找到 在排序后的块所在的位置,然后就可以直接加前缀和了,时间复杂度 ,在 时限下调下块长就能过了。
ABC341E
有时候区间的特定形态是可以用线段树维护的。对于每个区间考虑维护左端点值、右端点值、区间是否合法的标志 以及取反懒标记 。build 时把所有长度为一的区间标为合法,然后 pushup 时判断左儿子的右端点 和 右儿子的左端点是否不同,还要满足左右儿子区间都合法这个父亲区间才合法。修改的话就按套路对 取个反就行了。查询的话我们考虑用一个结构体记录合并到的区间的信息,包括左右端点值和是否合法的标记。然后在递归过程中一直判断就行了,看下代码吧。
这个题挺简单的,但赛时没过,显示出一些问题吧…… 首先想到一个做法一定不能草率地就直接开始写,要一步一步往下把每一步具体该怎么实现想出来,如果实现不了就假了,以及一定要想清楚正确性。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
#define ls now<<1
#define rs now<<1|1
const ll N=5*114514,M=1919810;
ll n,m,a[N];
struct tree{
ll l,r,fl,len;
ll lv,rv,tag;
}t[4*N];
void pushup(ll now){
t[now].fl=(t[ls].rv!=t[rs].lv)&&(t[ls].fl&&t[rs].fl);
t[now].lv=t[ls].lv,t[now].rv=t[rs].rv;
}
void pushdown(ll now){
if(!t[now].tag) return;
t[ls].tag^=1,t[rs].tag^=1;
t[ls].lv^=1,t[ls].rv^=1;
t[rs].lv^=1,t[rs].rv^=1;
t[now].tag=0;
return;
}
void build(ll now,ll l,ll r){
t[now].l=l,t[now].r=r,t[now].len=r-l+1;
t[now].lv=a[l],t[now].rv=a[r];
if(l==r){
t[now].fl=1;
return;
}
ll mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(now);
}
void update(ll now,ll x,ll y){
if(t[now].l>=x&&t[now].r<=y){
t[now].lv^=1,t[now].rv^=1;
t[now].tag^=1;
return;
}
pushdown(now);
ll mid=t[now].l+t[now].r>>1;
if(x<=mid) update(ls,x,y);
if(y>mid) update(rs,x,y);
pushup(now);
}
struct xx{
ll lv,rv,fl;
};
xx query(ll now,ll x,ll y){
if(t[now].l>=x&&t[now].r<=y) return (xx){t[now].lv,t[now].rv,t[now].fl};
ll mid=t[now].l+t[now].r>>1;
xx ans=(xx){-1,-1,-1},u;
pushdown(now);
if(x<=mid) ans=query(ls,x,y);
if(y>mid){
u=query(rs,x,y);
if(x<=mid) ans.fl=(ans.rv!=u.lv)&&(ans.fl&&u.fl),ans.rv=u.rv;
else ans=u;
}
pushup(now);
return ans;
}
void debug(ll now){
cout<<"QWQ: "<<t[now].l<<" "<<t[now].r<<" "<<t[now].lv<<" "<<t[now].rv<<" "<<t[now].fl<<'\n';
if(t[ls].len) debug(ls);
if(t[rs].len) debug(rs);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i){
char ch; cin>>ch;
a[i]=int(ch-'0');
}
build(1,1,n);
for(int i=1;i<=m;++i){
ll opt,l,r;
cin>>opt>>l>>r;
if(opt==1) update(1,l,r);
else{
xx sum=query(1,l,r);
if(sum.fl) cout<<"Yes\n";
else cout<<"No\n";
}
//debug(1);
}
return 0;
}
ARC058D
首先要知道:从 到 ,只能向下/右走,根据 数,可得路径数为
把要走的区域划分为两个矩形,然后把他们相邻的点的方案数乘起来求和就行了,画个图就直接看出来了,把题解的放一下:
注意组合数要判断方案数为 的情况。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3*114514,M=1919810,mod=1e9+7;
ll n,m,a,b;
ll p[N],q[N];
ll qpow(ll a,ll b){
ll ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
ll C(ll n,ll m){
if(n==m||m==0) return 1;
return p[n]*q[m]%mod*q[n-m]%mod;
}
ll calc(ll x,ll y,ll n,ll m){
return C(n-x+m-y,n-x);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m>>a>>b;
p[1]=1;
for(int i=2;i<=2e5;++i) p[i]=p[i-1]*i%mod;
for(int i=1;i<=2e5;++i) q[i]=qpow(p[i],mod-2);
ll ans=0;
for(int i=1;i<=n-a;++i){
ll x=calc(1,1,i,b),y=calc(i,b+1,n,m);
ans=(ans+x*y%mod)%mod;
}
cout<<ans;
return 0;
}//数据水了
ARC060E
就直接正着反着倍增跳就行了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll int
const ll N=114514,M=1919810;
ll n,a[N],L,Q,lg[N];
ll pos[N],sop[N];
ll f[N][24],g[N][24];
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
cin>>L>>Q;
for(int i=1;i<=n;++i) pos[i]=upper_bound(a+1,a+n+1,a[i]+L)-a-1;
for(int i=n;i>=1;--i){
ll l=1,r=i-1,res=0;
while(l<=r){
ll mid=(l+r)>>1;
if(a[i]-a[mid]<=L) res=mid,r=mid-1;
else l=mid+1;
}
sop[i]=res;
}
for(int i=n-1;i>=1;--i){
f[i][0]=pos[i];
for(int j=1;j<=18;++j){
f[i][j]=f[f[i][j-1]][j-1];
if(!f[i][j]) break;
}
}
for(int i=2;i<=n;++i){
g[i][0]=sop[i];
for(int j=1;j<=18;++j){
g[i][j]=g[g[i][j-1]][j-1];
if(!g[i][j]) break;
}
}
while(Q--){
ll x,y;
cin>>x>>y;
ll u,ans=0;
if(x<y){
u=x;
for(int i=18;i>=0;--i){
if(f[u][i]>y||!f[u][i]) continue;
u=f[u][i],ans+=pow(2,i);
if(u==y) break;
}
if(u<y) ++ans;
cout<<ans<<'\n';
}
else{
u=x;
for(int i=18;i>=0;--i){
if(g[u][i]<y||!g[u][i]) continue;
u=g[u][i],ans+=pow(2,i);
if(u==y) break;
}
if(u>y) ++ans;
cout<<ans<<'\n';
}
}
return 0;
}
ARC059E
设 表示 dp 到第 个人,总共已经发了 个糖时的总和。转移的话枚举一个 ,表示第 个人分到了 个糖,于是 的答案都要乘上一个 ,转移式就是:,珂以用前缀和预处理幂次,预处理 ,转移 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=514,M=1919810,mod=1e9+7;
ll n,c,a[N],b[N],ans;
ll po[N][N],sum[N][N],dp[N][N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>c;
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<=400;++i){
po[i][0]=1;
sum[i][0]=i;
for(int j=1;j<=400;++j)
po[i][j]=po[i][j-1]*i%mod,
sum[i][j]=(sum[i-1][j]+po[i][j])%mod;
}
dp[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=c;++j)
for(int k=0;k<=c-j;++k)
dp[i][j+k]=(dp[i][j+k]+dp[i-1][k]*(sum[b[i]][j]-sum[a[i]-1][j]+mod)%mod)%mod;
cout<<dp[n][c];
return 0;
}
ARC063E
两个做法,dp和贪心。
其实也不能严格算dp:先一遍dfs求出每个点的点权取值范围,然后再一遍dfs判断每两个点之间是否有交集,没有的话就不合法。
贪心:每次取点权最小的点然后对它周围没有点权的点赋值为 ,用一个优先队列维护,复杂度 。正确性感性理解一下是对的,严谨证明:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mk make_pair
const ll N=114514,M=1919810,inf=1e18;
struct xx{
ll next,to;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y){
e[++cnt].next=head[x];
e[cnt].to=y;
head[x]=cnt;
}
bool fl,vis[N];
ll n,k,a[N],rt,dept[N];
typedef pair<ll,ll> pi;
priority_queue<pi,vector<pi>,greater<pi> > q;
void dfs(ll u,ll fa){
if(fl) return;
for(int i=head[u];i;i=e[i].next){
ll v=e[i].to;
if(v==fa) continue;
if(abs(a[u]-a[v])!=1){
fl=1;
return;
}
dfs(v,u);
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n;
for(int i=1;i<n;++i){
ll a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
cin>>k;
for(int i=1;i<=k;++i){
ll v; cin>>v;
cin>>a[v];
if(!rt) rt=v;
q.push(mk(a[v],v));
}
while(!q.empty()){
ll u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next){
ll v=e[i].to;
if(a[v]||vis[v]) continue;
a[v]=a[u]+1,q.push(mk(a[v],v));
}
}
dfs(rt,0);
if(fl) cout<<"No";
else{
cout<<"Yes\n";
for(int i=1;i<=n;++i) cout<<a[i]<<'\n';
}
return 0;
}
ABC048D
顶多算个黄。简单手玩能发现只要中间有至少一个初始珂以删除的字符,那么就一定能把原串删到长度为 的时候,如果首尾相同就还能删一次,否则不能,然后总共能删奇数次就先手胜,否则后手胜。
ABC049D
开两个并查集,然后用一个二维map,直接对每个点加 m[find1(i)][find2(i)]++
就好了,查询也就直接查。
ARC064E
顶多算个绿。珂以把起点和终点看作两个半径为零的圆,每对圆之间受到宇宙射线照射的路径长就是圆心距离减去半径和。,我们就可以建一个完全图,把要受射线照射的路径长当作边权然后直接跑最短路就行了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define db long double
#define ll int
#define mk make_pair
const ll N=1145140,M=1919810;
const db inf=1145141919810.0;
struct xx{
ll next,to;
db val;
}e[2*N];
ll head[2*N],cnt;
void add(ll x,ll y,db z){
e[++cnt].next=head[x];
e[cnt].to=y;
e[cnt].val=z;
head[x]=cnt;
}
ll s,t,n;
db xs,ys,xt,yt,x[N],y[N],r[N];
db calc(ll i,ll j){
return sqrt((x[i]-x[j])*(x[i]-x[j])*1.0+(y[i]-y[j])*(y[i]-y[j])*1.0)-r[i]-r[j];
}
db dis[N];
bool vis[N];
typedef pair<db,ll> pi;
priority_queue<pi,vector<pi>,greater<pi> > q;
void dij(){
for(int i=0;i<=n+1;++i) dis[i]=inf,vis[i]=0;
dis[s]=0,q.push(mk(dis[s],s));
while(!q.empty()){
ll u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next){
ll v=e[i].to;
db w=e[i].val;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push(mk(dis[v],v));
}
}
}
}
int main(){
//ios::sync_with_stdio(0);
//cin.tie(0); cout.tie(0);
cin>>xs>>ys>>xt>>yt>>n;
s=0,t=n+1;
for(int i=1;i<=n;++i) cin>>x[i]>>y[i]>>r[i];
x[0]=xs,y[0]=ys,r[0]=0;
x[n+1]=xt,y[n+1]=yt,r[n+1]=0;
for(int i=0;i<=n+1;++i)
for(int j=i+1;j<=n+1;++j){
db w=calc(i,j);
w=max(w,(db)(0));
add(i,j,w),add(j,i,w);
}
dij();
printf("%0.10Lf",dis[n+1]);
return 0;
}