【TEST190401】数数题 & 贪心 & 圆方树

博客探讨了两道题目,一是关于岛屿问题,通过贪心策略优化从O(nm)到O(m)的解决方案,解释了贪心思路。二是介绍了在CAC问题中使用圆方树结合线段树和树链剖分的方法,讨论了优化复杂度的技巧。作者反思了在解题过程中思维的局限性和时间管理的重要性。
摘要由CSDN通过智能技术生成

island

在这里插入图片描述
来自出题人深深的恶意

咕了,直接贴题解和std:
在这里插入图片描述

#include <bits/stdc++.h>
#define P 998244353
#define N 1000002
using namespace std;
int n,L[N],R[N],re,fa[N],m,uu,s[N],o;
vector<int>V[N];
int h0,h1,h2,h3,h4;
int g0[N],g1[N],g2[N];long long v[8];
int fd(int x) {return !fa[x]?0:fa[x]==x?x:fa[x]=fd(fa[x]);}
int po(int a,int b) {
    int t=1;
    for (;b;b>>=1,a=1LL*a*a%P)
        if (b&1) t=1LL*t*a%P;
    return t;
}
void Re(int&x) {
    int f=0;char c;
    for (;!isdigit(c=getchar());) if (c=='-') f=1;x=c-48; 
    for (;isdigit(c=getchar());) x=x*10+c-48;f?x=-x:0;
}
int G(int l,int r) {
    int f0=h0,f1=(1LL*h1+h2+h3+h4)%P,f2=(h2+h3*3LL+h4*7LL)%P,f3=(h3+h4*6LL)%P,f4=h4;
    int a=((((f4*v[5]%P*(l-4)+f3*v[4])%P*(l-3)+f2*v[3])%P*(l-2)+f1*v[2])%P*(l-1)%P+f0)*l%P;
    int b=((((f4*v[5]%P*(r-3)+f3*v[4])%P*(r-2)+f2*v[3])%P*(r-1)+f1*v[2])%P*r%P+f0)*(r+1)%P;
    return (b-a)%P;
}
void F(int x,int o) {
    h0=(h0+1LL*g0[x]*g0[x]*o)%P;
    h1=(h1+2LL*g0[x]*g1[x]*o)%P;
    h2=(h2+(2LL*g0[x]*g2[x]+1LL*g1[x]*g1[x])*o)%P;
    h3=(h3+2LL*g1[x]*g2[x]*o)%P;
    h4=(h4+1LL*g2[x]*g2[x]*o)%P;
}
int wk(int *a) {
    for (int i=1;i<=m;i++) V[i].clear();
    for (int i=1;i<=n;i++) fa[i]=0,g0[i]=g1[i]=g2[i]=0;
    h0=h1=h2=h3=h4=uu=0;int re=0;
    for (int i=1;i<=n;i++)
        uu=(uu+a[i])%P,s[i]=a[i],
        re=(re+(a[i]+1LL)*a[i]/2)%P;
    sort(s+1,s+n+1);m=unique(s+1,s+n+1)-s-1;
    for (int i=1;i<=n;i++)
        a[i]=lower_bound(s+1,s+m+1,a[i])-s,
        V[a[i]].push_back(i);
    re=1LL*re*uu%P;
    for (int w=m;w;re=(re-G(s[w-1]+1,s[w]))%P,--w)
        for (int ii=0;ii<V[w].size();++ii) {
            int i=V[w][ii],ll=fd(i-1),rr=fd(i+1);
            if (ll) F(ll,-1),fa[ll]=i;
            if (rr) F(rr,-1),fa[rr]=i;
            g0[i]=(g0[ll]+g0[rr])%P;
            g1[i]=(g1[ll]+g1[rr])%P;
            g2[i]=(g2[ll]+g2[rr])%P;
            g0[i]=(g0[i]+s[w]+1)%P;g1[i]--;
            fa[i]=i;F(i,1);
        }
    return re;
}
int main() {
    freopen("island.in","r",stdin);
    freopen("island.out","w",stdout);
    cin>>n;char s[8];scanf(" %s",s);
    for (int i=1;i<=5;i++) v[i]=po(i,P-2);
    for (int i=1;i<=n;i++)
        Re(L[i]),Re(R[i]),L[i]=-L[i];
    int ul=0,ur=0,vl=0,vr=0;
    for (int i=1;i<=n;i++)
        ul=(ul+L[i]*(L[i]+1LL)/2)%P,vl=(vl+L[i])%P;
    for (int i=1;i<=n;i++)
        ur=(ur+R[i]*(R[i]-1LL)/2)%P,vr=(vr+R[i])%P;
    re=(re+1LL*ul*vr+1LL*ur*vl)%P;
    for (int i=1,u=0;i<=n;i++)
        re=(re+1LL*(L[i]+R[i])*i%P*u)%P,u=(1ll*u+L[i]+R[i])%P;
    for (int i=n,u=0;i;i--)
        re=(re-1LL*(L[i]+R[i])*i%P*u)%P,u=(1ll*u+L[i]+R[i])%P;
    re=(1LL*re+wk(L)+wk(R))%P;
    cout<<(re*2LL%P+P)%P<<endl;
    return 0;
}

*river

d p [ i ] [ j ] dp[i][j] dp[i][j]表示处于第 i i i个位置,且天数 % m = j \% m=j %m=j的最小天数。

发现可以贪心:第 i i i个位置的最优解必然由第 i − 1 i-1 i1个位置的最优解转移而来(也就是说确定局部最优解即可),因为考虑 j j j的状态, d p [ i ] [ j ] − d p [ i ] [ j − 1 ] ≡ 1 ( m o d m ) dp[i][j]-dp[i][j-1]\equiv 1\pmod m dp[i][j]dp[i][j1]1(modm),所以不存在两个状态分别覆盖 [ j , j ′ − 1 ] , [ j ′ , j − 1 ] [j,j&#x27;-1],[j&#x27;,j-1] [j,j1],[j,j1]的最优解,即存在最优解 k k k,使得 d p [ i ] [ j ] = d p [ j ] [ k ] + d i s ( k , j ) dp[i][j]=dp[j][k]+dis(k,j) dp[i][j]=dp[j][k]+dis(k,j)
(考试的时候没有想到这里,所以写的 O ( n m ) O(nm) O(nm)DP)

于是对于 0 − ( m − 1 ) 0-(m-1) 0(m1),处理出 g i g_i gi表示使得 a j + d i s ( i , j ) a_j+dis(i,j) aj+dis(i,j)最小的 j j j,则转移到第 i i i天时,最优策略是等到第 j j j天再走向下一个位置。

显然 m m m步以内会找到循环节(两次经过同一个模意义下的天数),直接乘上循环次数,再处理最后几步即可。

复杂度 O ( m ) O(m) O(m)
code from lvmaomao

#include<bits/stdc++.h>
using namespace std;

long long g[1000005];
long long a[1000005];
long long t[1000005];
long long cot[1000005],ans;
long long n,m,cnt;
long long pos;

int main()
{
    freopen("river.in","r",stdin);
    freopen("river.out","w",stdout);
    scanf("%lld%lld",&n,&m);n++;
    for(int i=1;i<=m;i++) scanf("%lld",&a[i]);
    a[0]=0x3f3f3f3f3f3f3f3f;
	pos=0;
    for(int i=1;i<=m;i++){
	  if(a[i]+i<=a[pos]+pos) pos=i;
	  g[i]=pos;
	}
	pos=0;
	for(int i=m;i>=1;i--){
	  if(a[i]+i<=a[pos]+pos) pos=i;
	  if(a[g[i]]+g[i]+m-pos>=a[pos]) g[i]=pos;
	}
    pos=1;
	while(t[pos]==0&&cnt<n-1){
	  t[pos]=++cnt;
	  ans=cot[pos]+a[g[pos]]+((g[pos]-pos+m)%m-1)%m+1;
	  if(t[(g[pos]+a[g[pos]]-1)%m+1]==0) cot[(g[pos]+a[g[pos]]-1)%m+1]=ans;
	  pos=(g[pos]+a[g[pos]]-1)%m+1;
	}
	if(cnt==n-1){
	  printf("%lld",ans);
	  return 0;
	}
	cnt++;
	ans+=(ans-cot[pos])*((n-t[pos])/(cnt-t[pos])-1);
	cnt=(((n-t[pos])/(cnt-t[pos]))*(cnt-t[pos]))+t[pos];
	while(cnt<n){
	  cnt++;
	  ans+=a[g[pos]]+((g[pos]-pos+m)%m-1)%m+1;
	  pos=(g[pos]+a[g[pos]]-1)%m+1;
	}
	printf("%lld",ans);
}

cac

建圆方树,线段树+树链剖分。

加操作直接把路径上所有圆点方点都 + v +v +v

对于点 x x x的询问,发现之前每次的加操作分成了三种类型( f a x fa_x fax为方点):

  • x x x f a x fa_x fax + v +v +v(均在路径上)
  • 只有 f a x + v fa_x+v fax+v x x x需要加上这部分的代价
  • 只有 x + v x+v x+v,要么 x x x L C A LCA LCA,要么 x x x是LCA这个方点的父亲结点,单独桶记录这个部分的值 d x d_x dx

发现前2种操作之和相当于线段树上 f a x fa_x fax的值。
所以答案即 f a x fa_x fax的值+ d x d_x dx

复杂度 O ( ( n + m ) + n log ⁡ 2 ( n + m ) ) O((n+m)+n\log ^2 (n+m)) O((n+m)+nlog2(n+m))

然而链修改单点查询有 n log ⁡ n n\log n nlogn的做法(树上差分,单点修改: x , y + 1 , L C A ( x , y ) − 2 x,y+1,LCA(x,y)-2 x,y+1,LCA(x,y)2,子树查询),所以复杂度还可以优化掉一个log

#include<bits/stdc++.h>
#define pb push_back
#define lc k<<1
#define rc k<<1|1
#define mid ((l+r)>>1)
using namespace std;
const int N=3e5+10,M=7e5+10,mod=998244353;

int n,m,q,dlt[N],num;
vector<int>g[N];
int head[M],to[M],nxt[M],tot;

char cp;
inline void rd(int &x)
{
	cp=getchar();x=0;int f=0;
	for(;!isdigit(cp);cp=getchar()) if(cp=='-') f=1;
	for(;isdigit(cp);cp=getchar()) x=x*10+(cp^48);
	if(f) x=-x;
}

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}

namespace tre{

int df[M],dfn,top[M],dep[M],f[M],son[M],sz[M];
int lzy[M<<2],z;

int dfs(int x,int fr)
{
	int i,j;f[x]=fr;dep[x]=dep[fr]+1;sz[x]=1;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];dfs(j,x);
		sz[x]+=sz[j];if(sz[son[x]]<sz[j]) son[x]=j;
	}
}

void mk(int x,int tpo)
{
	top[x]=tpo;df[x]=++dfn;
	if(!son[x]) return;
	mk(son[x],tpo);
	for(int j,i=head[x];i;i=nxt[i]){
		j=to[i];if(j==son[x]) continue;
		mk(j,j);
	}
}

void ins(int k,int l,int r,int L,int R)
{
	if(L<=l && r<=R) {ad(lzy[k],z);return;}
	if(L<=mid) ins(lc,l,mid,L,R);
	if(R>mid) ins(rc,mid+1,r,L,R);
}

inline void upd(int x,int y)
{
	z%=mod;if(z<0) z+=mod;
	for(;top[x]!=top[y];x=f[top[x]]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ins(1,1,num,df[top[x]],df[x]);
	}
	if(dep[x]<dep[y]) swap(x,y);
	ins(1,1,num,df[y],df[x]);
	if(y>n) ad(dlt[f[y]],z);
	else ad(dlt[y],z);
}

int ask(int k,int l,int r,int pos)
{
	if(l==r) return lzy[k];
	if(pos<=mid) return inc(ask(lc,l,mid,pos),lzy[k]);
	return inc(ask(rc,mid+1,r,pos),lzy[k]);
}

inline void sol()
{
	int op,x,y,ans;dfs(1,0);mk(1,1);
	for(;q;--q){
		rd(op);rd(x);
		if(!op) {rd(y);rd(z);upd(x,y);}
		else{
			if(f[x]) ans=ask(1,1,num,df[f[x]]);else ans=0;
		   printf("%d\n",inc(ans,dlt[x]));	
		}
	}
}

}

namespace tar{

int cnt,low[N],df[N],stk[N],top;

void ck(int x)
{
	int i,j,tp;df[x]=low[x]=++cnt;stk[++top]=x;
	for(i=g[x].size()-1;i>=0;--i){
		j=g[x][i];
		if(!df[j]){
			ck(j);
			if(low[j]>=df[x]){
				num++;lk(x,num);
				for(;;){
					tp=stk[top--];lk(num,tp);
					if(tp==j) break;
				}
			}else low[x]=min(low[x],low[j]);
		}else low[x]=min(low[x],df[j]);
	}
}
}

int main(){
	freopen("cac.in","r",stdin);
	freopen("cac.out","w",stdout);
	int i,x,y;rd(n);rd(m);rd(q);
	for(i=1;i<=m;++i){
		rd(x);rd(y);
		g[x].pb(y);g[y].pb(x);
	}
	num=n;tar::ck(1);tre::sol();
	fclose(stdin);fclose(stdout);
	return 0;
}

总结

T1不应该迟疑拖沓,这种不可做题应该拿完暴力分就立刻走人
然而浪费了2h+

T2可能还是套路见少了,思维没有拓展性,没有大胆贪心,一直“以为”局部最优解不是全局最优解。。。

T3浪费了点时间打暴力,后面才发现自己会(而且想了有点久)。。。

总的来说,T2是一道很可得分的题,代码难度也不高,前提是想对了方向,但我还是没有深入分析出DP的本质就是贪心,考试中错过这样一道题非常可惜。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值