星期一:
cf round 960 div2 B 简单构造 cf传送门
题意有点绕
思路:开始容易想到 y前和 x后全-1,y到x填1的构造,但对于 5 2 1,1 1 -1 -1 -1有问题,1和5的后缀值都为 -1,y判定为5
实际两边不需要全填-1,首先明确x和y的位置必填1,且y-1和x+1必填-1,此后-1和1交替填就行,若头尾是1,会出现错误吗,事实上是不会的,因为x严格大于y,故x至y的和即前后缀最大值>=2若头尾是-1,前后缀最大值>=1
代码如下:
const int N=2e6+10,M=210;
ll n;
int a[N];
void solve(){
int x,y; cin >> n >> x >> y;
a[y]=a[x]=1;
for(int i=y-1;i;i--) a[i]=-a[i+1];
for(int i=x+1;i<=n;i++) a[i]=-a[i-1];
for(int i=y;i<=x;i++) a[i]=1;
for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
}
贴 ABC322 F atc传送门
题意:询问01串中最长连续1的个数
思路:普通线段树题,贴上的原因是提醒下自己,merge时,如果存在懒标记,一定要初始化为0赛时忘了导致调试浪费了些许时间
代码如下:
struct nod{
int l,r;
ll mal0,mal1,mar0,mar1,ma0,ma1;
int tag;
}t[N];
ll ql,qr;
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.mal0=a.mal0==a.r-a.l+1?a.mal0+b.mal0:a.mal0;
res.mal1=a.mal1==a.r-a.l+1?a.mal1+b.mal1:a.mal1;
res.mar0=b.mar0==b.r-b.l+1?a.mar0+b.mar0:b.mar0;
res.mar1=b.mar1==b.r-b.l+1?a.mar1+b.mar1:b.mar1;
res.ma0=max({a.ma0,b.ma0,a.mar0+b.mal0});
res.ma1=max({a.ma1,b.ma1,a.mar1+b.mal1});
res.tag=0; //注意懒标记初始化
return res;
}
贴 ABC044 C atc传送门
思路:和上周日补的题很像,都是背包dp
dp【i】【j】【k】表示考虑到第 i个,拿了 j个,总和为 k的方案数
代码如下(二维优化:
ll n;
int x[55],sum[55];
ll dp[55][2510];
void solve(){
int a; cin >> n >> a;
for(int i=1;i<=n;i++){
cin >> x[i];
sum[i]=sum[i-1]+x[i];
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int k=sum[i];k>=x[i];k--) //因为优化了第一维,所以k无需枚举到1
for(int j=i;j;j--) dp[j][k]+=dp[j-1][k-x[i]];
}
ll ans=0;
for(int i=1;i<=n;i++) ans+=dp[i][i*a];
cout << ans;
}
补 cf edu round124 D cf传送门
题意:给n个点,对每个点给出一个曼哈顿距离最小且不在点集内的点
思路:刚开始以为要用啥不会的算法。。实则不然,我们可以很轻易先知道答案距离为1的点,而如果一点答案不为1,但挨着一个答案为1的点,那么可得此点答案为2,同理可得一点答案为3,到此可知 bfs处理即可
代码如下:
const int N=2e6+10,M=210;
ll n;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
struct nod2{
int x,y;
bool ifa;
};
nod2 ans[N];
void solve(){
cin >> n;
map<PII,int>mp;
for(int i=1;i<=n;i++){
int x,y; cin >> x >> y;
mp[{x,y}]=i;
}
struct nod{
int x,y;
PII an;
};
queue<nod>qu;
for(auto [xy,i]:mp){
int x=xy.first,y=xy.second;
for(int j=0;j<4;j++){
int xx=x+dx[j],yy=y+dy[j];
if(!mp.count({xx,yy})){
qu.push({x,y,{xx,yy}}),ans[i]={xx,yy,1};
break;
}
}
}
while(!qu.empty()){
nod t=qu.front(); qu.pop();
auto [a1,a2]=t.an;
for(int i=0;i<4;i++){
int x=t.x+dx[i],y=t.y+dy[i];
if(mp.count({x,y}) && !ans[mp[{x,y}]].ifa){
ans[mp[{x,y}]]={a1,a2,1};
qu.push({x,y,{a1,a2}});
}
}
}
for(int i=1;i<=n;i++) cout << ans[i].x << " " << ans[i].y << "\n";
}
补 cf edu round124 C cf传送门
思路:画下图发现a的头尾和b的头尾这四个端点必须和对面连线,开始思路不清晰写出一坨,实际分类讨论下边数的情况就行,先把四个端点连接的最小代价求出,然后讨论连四条,两条,三条
代码如下:
const int N=2e5+10,M=210;
ll n;
ll a[N],b[N];
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) cin >> b[i];
ll a1=1e18,an=1e18,b1=1e18,bn=1e18;
for(int i=1;i<=n;i++){
a1=min(abs(b[i]-a[1]),a1);
an=min(abs(b[i]-a[n]),an);
b1=min(abs(a[i]-b[1]),b1);
bn=min(abs(a[i]-b[n]),bn);
}
ll ans=a1+an+b1+bn;
ans=min({
ans,
abs(a[1]-b[1])+abs(a[n]-b[n]),
abs(a[1]-b[n])+abs(a[n]-b[1]),
abs(a[1]-b[1])+an+bn,
abs(a[1]-b[n])+an+b1,
abs(a[n]-b[1])+a1+bn,
abs(a[n]-b[n])+a1+b1
});
cout << ans << "\n";
}
近期准备重拾期望dp
先重做期望dp入门题 魔镜 cf传送门
思路: dp[ i ]表示从第1天到第 i天且高兴的期望天数
对于第 i面魔镜,有两种情况: (以下将 pi/100 简称为 pi
第一种情况:回答漂亮,则高兴,概率为 pi, 天数为 dp[ i-1 ]+1
第二种情况:回答不漂亮,概率为 1-pi, 天数为 dp[ i-1 ]+1+dp[ i ]
则得转移式 dp[ i ] = pi * ( dp[ i-1 ] +1 ) + ( 1-pi ) * ( dp[ i-1 ]+1+dp[ i ] ) 化简后得 dp[ i ] = 100 * ( dp[ i-1 ]+1 ) / pi , 线性转移即可
代码如下:
const int mod=998244353;
ll n;
ll qpow(ll a,int n){
ll res=1;
while(n){
if(n&1) (res*=a)%=mod;
(a*=a)%=mod;
n>>=1;
}
return res;
}
void solve(){
cin >> n;
ll ans=0;
for(int i=1;i<=n;i++){
int p; cin >> p;
ans=100*(ans+1)%mod*qpow(p,mod-2)%mod;
}
cout << ans;
}
期望dp入门题2 逃离这棵树 mtj传送门
思路: dp[ i ]表示从 i开始到叶子节点即逃离的期望时间,叶子节点的期望时间即为0
对于一个非叶子节点 x,存在两种情况: (以下将移动和不动的概率简称为 q和 p
第一种情况:移动到了一个子节点 v,概率为qv,时间为 dp[ v ]+1
第二种情况:原地不动,概率为 px,时间为 dp[ x ]+1
则得转移式: 化简后为:
代码如下:
const int N=2e6+10;
const int mod=998244353;
ll n;
ll qpow(ll a,int n){
ll res=1;
while(n){
if(n&1) (res*=a)%=mod;
(a*=a)%=mod;
n>>=1;
}
return res;
}
int p[N];
ll dp[N];
vector<PII>ve[N];
void dfs(int x){
ll sum=p[x],qdpv=0; //注意qdpv需开ll
for(auto [q,v]:ve[x]){
sum+=q;
dfs(v);
qdpv+=q*dp[v];
}
dp[x]=(sum+qdpv)%mod*qpow(sum-p[x],mod-2)%mod;
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> p[i];
for(int i=2;i<=n;i++){
int f,q; cin >> f >> q;
ve[f].push_back({q,i});
}
dfs(1);
cout << dp[1];
}
状压dp题单第一题 互不侵犯 牛客传送门
思路:状压dp板子
代码如下:
ll n;
int s[1<<10],ma[1<<10],cnt;
ll dp[11][110][1<<10];
void solve(){
int k; cin >> n >> k;
for(int mask=0;mask<1<<n;mask++){
if(mask&(mask<<1)) continue;
ma[cnt++]=mask; //注意是cnt++
s[mask]=__builtin_popcount(mask);
}
dp[0][0][0]=1;
for(int i=1;i<=n+1;i++){
for(int j=0;j<=k;j++){
for(int a=0;a<cnt;a++){
for(int b=0;b<cnt;b++){
if(j<s[ma[a]]+s[ma[b]]) continue;
if(ma[a]&ma[b] || ma[a]&ma[b]<<1 || ma[a]&ma[b]>>1) continue;
dp[i][j][a]+=dp[i-1][j-s[ma[a]]][b];
}
}
}
}
cout << dp[n+1][k][0];
}
星期二:
补24钉耙编程联赛2 1006 smu传送门
思路:单独算出每个节点的期望停留时间,寻找一条最长的链
一件事成功概率为p,那么完成它的期望时间则为 1/p,可以先把期望时间的分母都化为1-15的lcm最后再约分
代码如下:
const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int lc=360360; //lcm(1,2...15)
int p[N];
ll ans;
void dfs(int x,int f,ll sum){
ans=max(sum,ans);
for(int v:ve[x]) if(v!=f)
dfs(v,x,sum+p[v]);
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) ve[i].clear();
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
for(int i=1;i<=n;i++){
int pi; cin >> pi;
p[i]=15*lc/pi;
}
ans=0;
dfs(1,0,p[1]);
int gc=__gcd(ans,1ll*lc);
cout << ans/gc << "/" << lc/gc << "\n";
}
24牛客多校3 B 牛客传送门
思路:赛时被折磨半天,最后猜了个gcd的结论过了,然后osir反应过来是裴蜀定理
代码如下:
ll n;
void solve(){
ll D; cin >> n >> D;
ll gc=0;
for(int i=1;i<=n;i++){
ll h; cin >> h;
gc=__gcd(gc,h);
}
cout << min(D%gc,abs(D%gc-gc)); //注意判反弹的情况
}
补 ABC344 F atc传送门
思路:很挑战我能力的一道dp,dp【i】【j】【0/1】表示到达 ( i, j )最少的步数及剩余金钱
dis【k】【y】表示从 i,j到点的最少需要金钱。可想到停留拿钱的P一定是递增的关系,且攒到足够走的钱就走,四重枚举( i,j )走到( k,y )的最少步数和剩余金钱,攒钱点之间的移动取最小代价走就行
代码如下:
ll n;
ll p[88][88],r[88][88],d[88][88];
ll dis[88][88],dp[88][88][2];
void solve(){
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin >> p[i][j],dp[i][j][0]=1e18;
for(int i=1;i<=n;i++)
for(int j=1;j<n;j++)
cin >> r[i][j];
for(int i=1;i<n;i++)
for(int j=1;j<=n;j++)
cin >> d[i][j];
dp[1][1][0]=dp[1][1][1]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
memset(dis,0x3f,sizeof dis);
dis[i][j]=0;
for(int k=i;k<=n;k++)
for(int y=j;y<=n;y++){
if(k>1) dis[k][y]=min(dis[k-1][y]+d[k-1][y],dis[k][y]);
if(y>1) dis[k][y]=min(dis[k][y-1]+r[k][y-1],dis[k][y]);
}
for(int k=i;k<=n;k++)
for(int y=j;y<=n;y++){
if(k!=n && y!=n && p[k][y]<=p[i][j]) continue;
auto [step,coin]=dp[i][j];
ll stay=max(0ll,(dis[k][y]-coin+p[i][j]-1)/p[i][j]); //向上取整
ll ncoin=coin+stay*p[i][j]-dis[k][y];
ll nstep=step+stay+k-i+y-j;
if(nstep<dp[k][y][0] || nstep==dp[k][y][0] && ncoin>dp[k][y][1]){
dp[k][y][0]=nstep,dp[k][y][1]=ncoin; //&&优先级更高
}
}
}
}
cout << dp[n][n][0];
}
补cf round500 div1 B cf传送门
思路:初见这题没有思路,没想到是要往图论的方向去想.
n行m列可以看为是一个二分图,左边n个点,右边m个点,而(n,m)的标记即为连接n点和m点的一条边,那么最后则是要把此图变为完全二分图,也就是存在 n*m条边
通过给出的操作,能将图上的连通块变为完全连通块,所以加边则是要把连通块连一起即可,答案即为连通块的数量减一
代码如下:
const int N=2e6+10;
ll n;
int fa[N];
int fnd(int x){
return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void solve(){
int m,q; cin >> n >> m >> q;
for(int i=1;i<=n+m;i++) fa[i]=i;
while(q--){
int r,c; cin >> r >> c;
c+=n; //防止编号重复
r=fnd(r),c=fnd(c);
if(r!=c) fa[r]=c;
}
ll ans=-1;
for(int i=1;i<=n+m;i++) ans+=(fa[i]==i);
cout << ans;
}
星期三:
洛谷P3388 tarjan割点 洛谷传送门
思路:tarjan求割点的模板
代码如下:
const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int dfn[N],low[N],tot;
bool cut[N];
int root;
void tarjan(int x){
dfn[x]=low[x]=++tot;
int child=0;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[v],low[x]);
if(low[v]>=dfn[x]){
child++;
if(x!=root || child>1) cut[x]=1; //ans++跟在这后面会出错,目前不到为啥
}
}else low[x]=min(dfn[v],low[x]);
}
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
for(int i=1;i<=n;i++) if(!dfn[i]) root=i,tarjan(i);
int ans=0;
for(int i=1;i<=n;i++) ans+=(cut[i]);
cout << ans << "\n";
for(int i=1;i<=n;i++) if(cut[i]) cout << i << " ";
}
河南萌新联赛 二,被模拟题折磨的神志不清,一度摆烂,最后看见一道字符串哈希,ac完成救赎
牛客传送门
思路:一眼字符串哈希,处理出a的26个字母的目标哈希值,再枚举移多少位,比较哈希值
注意没出现过的字符不参与比较,否则也会计入答案
代码如下:
const int mod=998244353;
ll n;
ll num[27],ha[N][27];
ll p[N];
const int ba=13331;
int g[27];
void solve(){
cin >> n;
string a,b; cin >> a >> b; a=" "+a,b=" "+b;
p[0]=1;
for(int i=1;i<=n;i++){
g[a[i]-'a'+1]=g[b[i]-'a'+1]=-1;
p[i]=p[i-1]*ba%mod;
for(int j=1;j<=26;j++){
char c='a'+j-1;
num[j]=(num[j]*ba+(a[i]==c))%mod;
ha[i][j]=(ha[i-1][j]*ba+(b[i]==c))%mod;
}
}
int ans=0;
for(int i=0;i<=n;i++){ //枚举移i位,注意从0开始
for(int j=1;j<=26;j++) if(g[j]==-1){ //枚举字母
//假如b为"abcd",现在要得到"cd ab"的哈希值
ll h=(ha[n][j]-ha[i][j]*p[n-i]%mod+mod)%mod*p[i]%mod; //cd的哈希值
(h+=ha[i][j])%=mod; //ab的哈希值
if(h==num[j]) ans++,g[j]=1;
}
}
cout << ans;
}
星期四:
洛谷P1656 洛谷传送门
思路:tarjan求割边板子题
代码如下:
const int N=2e6+10,M=210;
ll n;
vector<PII>eg; //存边
vector<int>ve[N]; //存点的出边
int dfn[N],low[N],tot;
PII bri[N];int cnt; //存割边
void add(int u,int v){
eg.push_back({u,v});
ve[u].push_back(eg.size()-1); //存出边的编号
}
void tarjan(int x,int in_e){ //点和入边
dfn[x]=low[x]=++tot;
for(int i=0,sz=ve[x].size();i<sz;i++){
int j=ve[x][i],v=eg[j].second; //边和目标点
if(!dfn[v]){
tarjan(v,j);
low[x]=min(low[v],low[x]);
if(low[v]>dfn[x]) bri[++cnt]={x,v}; //满足条件则此边为桥
}else if(j!=(in_e^1)) low[x]=min(dfn[v],low[x]); //j需不是入边的反边
}
}
void solve(){
int m; cin >> n >> m;
while(m--){
int a,b; cin >> a >> b;
add(a,b),add(b,a);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
sort(bri+1,bri+cnt+1);
for(int i=1;i<=cnt;i++) cout << bri[i].first << " " << bri[i].second << "\n";
}
补24河南萌新 一 L 牛客传送门
思路:tarjan割边+多重背包优化的板子题
tarjan把割边标记出来,dfs处理出连通块数量和大小,然后背包处理可行性,然而把所有连通块用01背包处理显然会超时,我们把相同大小的连通块放一起,用多重背包处理,以及加上一点优化
代码如下(二进制优化多重背包 :
const int N=2e6+10,M=210;
ll n;
vector<PII>eg;
vector<int>ve[N];
int dfn[N],low[N],tot;
//PII bri[N];int cnt;
bool bri[N],vi[N]; //由存桥改为对桥打上标记
void add(int u,int v){
eg.push_back({u,v});
ve[u].push_back(eg.size()-1);
}
void tarjan(int x,int in_e){
dfn[x]=low[x]=++tot;
for(int i=0,sz=ve[x].size();i<sz;i++){
int j=ve[x][i],v=eg[j].second;
if(!dfn[v]){
tarjan(v,j);
low[x]=min(low[v],low[x]);
// if(low[v]>dfn[x]) bri[++cnt]={x,v};
if(low[v]>dfn[x]) bri[j]=bri[j^1]=1;
}else if(j!=(in_e^1)) low[x]=min(dfn[v],low[x]);
}
}
int dfs(int x){
int res=1;
for(int i=0,sz=ve[x].size();i<sz;i++){
int j=ve[x][i],v=eg[j].second;
if(vi[v] || bri[j]) continue;
vi[v]=1;
res+=dfs(v);
}
return res;
}
ll dp[N];
void solve(){
int m; cin >> n >> m;
while(m--){
int a,b; cin >> a >> b;
add(a,b),add(b,a);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
map<int,int>mp;
for(int i=1;i<=n;i++) if(!vi[i]) vi[i]=1,mp[dfs(i)]++;
vector<PII>blo;ll sum=0; //fi-价值,se-重量,不过此题中重量和价值相等
for(auto [v,s]:mp){
for(ll i=1;i<=s;i<<=1){
blo.push_back({i*v,i*v}),sum+=i*v;
s-=i;
}
if(s) blo.push_back({s*v,s*v}),sum+=s*v; //二进制优化
}
dp[0]=1;
for(int i=0,sz=blo.size();i<sz;i++)
for(int j=sum;j>=blo[i].second;j--) dp[j]+=dp[j-blo[i].second];
ll ans=0;
for(int i=1;i<=sum;i++) if(dp[i]) ans=max(1ll*i*(sum-i),ans);
cout << ans << "\n";
}
星期五:
补 ABC225 F atc传送门
思路:用到了一个很新的排序,实际上把s作为base进制数推导一下,其实是具有排序的传递性
关于为什么要倒着dp,似懂非懂,还需沈科历届
代码如下:
ll n;
string a[55];
string dp[55][55];
void solve(){
int k; cin >> n >> k;
for(int i=1;i<=n;i++){
cin >> a[i];
for(int j=1;j<=k;j++) dp[i][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
}
// for(int j=1;j<=k;j++) dp[0][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
for(int j=1;j<=k;j++) dp[n+1][j]="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
sort(a+1,a+n+1,[](string s1,string s2){
return s1+s2<s2+s1;
});
// for(int i=1;i<=k;i++) s.append(a[i]);
// for(int i=1;i<=n;i++){
// for(int j=1;j<=k;j++){
// dp[i][j]=min(dp[i-1][j-1]+a[i],dp[i-1][j]);
// }
// }
for(int i=n;i;i--){
for(int j=1;j<=k;j++)
dp[i][j]=min(a[i]+dp[i+1][j-1],dp[i+1][j]);
}
// for(int i=1;i<=n;i++) cout << a[i] << " \n"[i==n];
// for(int i=1;i<=n;i++){
// for(int j=1;j<=k;j++) cout << dp[i][j] << " \n"[j==k];
// }
cout << dp[1][k];
}
多重背包单调队列优化 洛谷P1776 洛谷传送门
思路:练习多重背包的板子,虽跟着视频学了并敲了单调队列优化,仍需沈科历届
代码如下:
const int N=2e6+10,M=210;
ll n;
ll dp[N][2];
int q[N];
void solve(){
ll m; cin >> n >> m;
for(int i=1;i<=n;i++){
ll v,w,s; cin >> v >> w >> s;
for(int d=0;d<w;d++){
int h=1,t=0;
for(int j=d;j<=m;j+=w){
while(h<=t && dp[j][0]>=dp[q[t]][0]+(j-q[t])/w*v) t--;
q[++t]=j;
if(h<=t && q[h]<j-s*w) h++;
if(h<=t) dp[j][1]=max(dp[q[h]][0]+(j-q[h])/w*v,dp[j][0]);
}
}
for(int j=0;j<=m;j++) dp[j][0]=dp[j][1],dp[j][1]=0;
}
cout << dp[m][0];
}
补 ABC156 D atc传送门
思路:之前那个组合数不能取模用不上了,用逆元求组合数可行,复杂度为O(m)
代码如下:
ll n;
ll qpow(ll a,ll n){
ll res=1;
while(n){
if(n&1) (res*=a)%=mod;
(a*=a)%=mod;
n>>=1;
}
return res;
}
ll c(int n,int m){
ll fz=1,fm=1;
for(int i=1;i<=m;i++) (fm*=i)%=mod;
for(int i=n;i>n-m;i--) (fz*=i)%=mod;
return fz*qpow(fm,mod-2)%mod;
}
void solve(){
int a,b; cin >> n >> a >> b;
ll ans=(qpow(2,n)-1+mod)%mod;
ans=(ans-c(n,a)+mod)%mod;
ans=(ans-c(n,b)+mod)%mod;
cout << ans << "\n";
}
星期六:
补 ABC178 C atc传送门
思路:开局想到一个错解,Cn,2 * 10^(n-2),会有大量重复的情况
考虑容斥原理,所有序列有10^n种,减去没有0和没有9的9^n种序列,再加上重复减去的即没0也没9的8^n种情况
代码如下:
ll n;
ll qpow(ll a,ll n){
ll res=1;
while(n){
if(n&1) (res*=a)%=mod;
(a*=a)%=mod;
n>>=1;
}
return res;
}
void solve(){
cin >> n;
ll ans=(qpow(10,n)-2*qpow(9,n)+qpow(8,n))%mod;
(ans+=mod)%=mod;
cout << ans;
}
补 ABC263 D atc传送门
思路:挺简单一题,但赛时没出。枚举 x的取值,在 i+1到 n中找到最小值的 y,和ans取min即
赛时在找最小值的操作中想用线段树,但捣鼓了下发现并不能实现,首先寻找最小值是有范围限制的,其次其实找的是最小值的下标,试了下线段树上二分,发现也不能实现,因为不能知道子节点的最小值是不是在范围内的
其实完全不需要那么麻烦,整个右操作的后缀最小值数组,开pair类型second带下标即可
更新:其实下标也不需要带,只需要维护后缀最小值即可,那这样又可以用线段树了
代码如下:
const int N=2e6+10,M=210;
ll n;
ll a[N];
PII ar[N];
void solve(){
ll l,r; cin >> n >> l >> r;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]+=a[i-1];
}
ar[n+1]={a[n],n+1};
for(int i=n;i;i--){
ar[i].first=r*(n-i+1)+a[i-1];
if(ar[i].first<ar[i+1].first) ar[i].second=i;
else ar[i]=ar[i+1];
}
ll ans=1e18;
for(int i=0;i<=n;i++){
ll al=i*l;
// int idx=ar[i+1].second;
// ans=min(al+ar[idx].first-a[i],ans);
ans=min(al+ar[i+1].first-a[i],ans);
}
cout << ans;
}
补 ABC272 E atc传送门
思路:开局毫无头猪,想不到什么算法能够实现,其实也确实不用什么算法,因为就是暴力
长度为 n序列 mex的范围也必在 [0,n-1]间,所以对于 ai只考虑其值在范围内的操作次数范围,i为1时有效操作最多 n次,i为2时有效操作次数最多 n/2次,为3最多n/3次……,又遇到了一个熟悉又陌生的玩意儿:调和级数。1+1/2+1/3+1/4+...近似于 logn,所以上述暴力复杂度为 O(nlogn)
代码如下:
const int N=2e6+10,M=210;
ll n;
ll a[N];
vector<int>ve[N];
void solve(){
ll m; cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i];
if(a[i]>=n) continue;
int l=a[i]>=-i?1:(-a[i]+i-1)/i;
int r=min(m,(n-a[i])/i); //有效操作次数的范围
for(int j=l;j<=r;j++) ve[j].push_back(a[i]+1ll*i*j);
}
for(int i=1;i<=m;i++){
unordered_map<int,bool>ex;
for(auto v:ve[i]) ex[v]=1;
int ans=0;
while(ex[ans]) ans++;
cout << ans << "\n";
}
}
补 ABC199 E atc传送门
思路:神奇的状压转移方案,思路不是很清晰,仍需沈科历届
代码如下:
ll n;
ll dp[1<<19];
vector<PII>ve[110];
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int x,y,z; cin >> x >> y >> z;
ve[x].push_back({y,z});
}
dp[0]=1;
for(int mask=0;mask<1<<n;mask++){
vector<int>num;
for(int i=0;i<n;i++) if(mask&1<<i) num.push_back(i+1);
bool ifz=1;
for(auto [y,z]:ve[__builtin_popcount(mask)]){
int sum=0;
for(auto v:num) sum+=(v<=y);
if(sum>z){ifz=0; break;}
}
if(ifz) for(auto i:num) dp[mask]+=dp[mask^(1<<i-1)];
}
cout << dp[(1<<n)-1];
}
周日:
做道24钉耙编程联赛3的线段树 vj传送门
思路: 用sta变量维护一个区间的状态,2表示元素相同,3表示升序,4表示降序,5表示单峰,-1非法状态
这样写的坏处是merge会写成依托,因为 sta的合并需要很详细的分类讨论,在此不赘述
代码如下:
const int N=2e6+10,M=210;
ll n;
struct seg_Tree{
#define lc p<<1
#define rc p<<1|1
struct nod{
int l,r;
ll le,ri; //左右端点的值
ll add;
int sta; //表示区间的状态
}t[N];
ll ql,qr,qv;
nod merge(nod a,nod b){
nod res;
res.l=a.l,res.r=b.r;
res.add=0;
res.le=a.le,res.ri=b.ri;
if(a.sta==2){
if(a.l==a.r){
if(b.l==b.r){
if(a.ri==b.le) res.sta=2;
if(a.ri<b.le) res.sta=3;
if(a.ri>b.le) res.sta=4;
}else{
if(b.sta==2 && a.ri==b.le) res.sta=2;
else if(b.sta==3 && a.ri<b.le) res.sta=3;
else if(b.sta==4 && a.ri>b.le) res.sta=4;
else if(b.sta==4 && a.ri<b.le) res.sta=5;
else if(b.sta==5 && a.ri<b.le) res.sta=5;
else res.sta=-1;
}
}else{
if(b.sta==2 && a.ri==b.le) res.sta=2;
else res.sta=-1;
}
}else if(a.sta==3){
if(b.sta==3 && a.ri<b.le) res.sta=3;
else if(b.sta==4) res.sta=5;
else if(b.sta==5 && a.ri<b.le) res.sta=5;
else if(b.sta==2 && b.l==b.r){
if(a.ri<b.le) res.sta=3;
else if(a.ri>b.le) res.sta=5;
else res.sta=-1;
}else res.sta=-1;
}else if(a.sta==4){
if(b.sta==4 && a.ri>b.le) res.sta=4;
else if(b.sta==2 && b.l==b.r && a.ri>b.le) res.sta=4;
else res.sta=-1;
}else if(a.sta==5){
if(b.sta==4 && a.ri>b.le) res.sta=5;
else if(b.sta==2 && b.l==b.r && a.ri>b.le) res.sta=5;
else res.sta=-1;
}else res.sta=-1;
return res;
}
void pushup(int p){t[p]=merge(t[lc],t[rc]);}
void pushdn(int p){
if(!t[p].add) return ;
t[lc].le+=t[p].add,t[lc].ri+=t[p].add;
t[lc].add+=t[p].add;
t[rc].le+=t[p].add,t[rc].ri+=t[p].add;
t[rc].add+=t[p].add;
t[p].add=0;
}
void bd(int p,int l,int r){
t[p]={l,r,0,0,0,2};
if(l==r){
int num; cin >> num;
t[p].le=t[p].ri=num;
return ;
}
int mid=l+r>>1;
bd(lc,l,mid);
bd(rc,mid+1,r);
pushup(p);
}
void update(int p){
if(ql<=t[p].l && qr>=t[p].r){
t[p].le+=qv,t[p].ri+=qv;
t[p].add+=qv;
return ;
}
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql<=mid) update(lc);
if(qr>mid) update(rc);
pushup(p);
}
void updt(int l,int r,ll v){
ql=l,qr=r;
qv=v;
update(1);
}
nod query(int p){
if(ql<=t[p].l && qr>=t[p].r) return t[p];
int mid=t[p].l+t[p].r>>1;
pushdn(p);
if(ql>mid) return query(rc);
if(qr<=mid) return query(lc);
return merge(query(lc),query(rc));
}
int ask(int l,int r){
ql=l,qr=r;
return query(1).sta;
}
}tr;
void solve(){
cin >> n;
tr.bd(1,1,n);
int q; cin >> q;
while(q--){
int op; cin >> op;
int l,r; cin >> l >> r;
if(op==1){
int x; cin >> x;
tr.updt(l,r,x);
}else{
if(l==r) op=2;
int sta=tr.ask(l,r);
if(sta==op) cout << "1\n";
else cout << "0\n";
}
}
}