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
i−1个位置的最优解转移而来(也就是说确定局部最优解即可),因为考虑
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][j−1]≡1(modm),所以不存在两个状态分别覆盖
[
j
,
j
′
−
1
]
,
[
j
′
,
j
−
1
]
[j,j'-1],[j',j-1]
[j,j′−1],[j′,j−1]的最优解,即存在最优解
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−(m−1),处理出 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的本质就是贪心,考试中错过这样一道题非常可惜。