考场错误:
今天的题目难度更高,在读了A题时,感觉数据范围很大,没看出来这是签到题,以为B是签到,结果浪费了一些时间,写出A后,又开始犹豫了B和C看起来都挺可做,反复徘徊,最后看了有人过了字符串的H,便去写H,想了个根号分治
O
(
n
n
l
o
g
n
)
O(n\sqrt{n}log n)
O(nnlogn)需要卡快长,毒瘤题还卡自然溢出,遂修改了很久,最后半个小时会了C的贪心策略,去写树剖,因为int x,y; 结果没读入挂了,没查出来,最后也没能通过,因此今天仅通过了两道题,rk7
Gym - 101741C
贪心,这类题目需要多加练习,寻找感觉
考虑我们把给定的m条边按照lca的深度从大到小排序,然后依次检查,如果这段路径上没有点,那么就把lca选中,如果路径上有点,那么不需要操作,实现过程使用树链剖分和树状数组
这样做是对的,因为我们每次尽量把选择的点深度尽量提高,这样就能对后续产生更多的贡献啦
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
vector <int> G[maxn];
int n,sz[maxn],son[maxn],tp[maxn];
int fa[maxn],dfn[maxn],dep[maxn];
void dfs(int u)
{
sz[u]=1;
// cerr<<u<<endl;
for(int to:G[u])
{
if(to==fa[u]) continue;
fa[to]=u; dep[to]=dep[u]+1;
dfs(to);
sz[u]+=sz[to];
if(sz[to]>sz[son[u]]) son[u]=to;
}
}
void dfss(int u,int ff)
{
// cerr<<u<<" "<<son[u];
tp[u]=ff; dfn[u]=++dfn[0];
if(son[u])
dfss(son[u],ff);
for(int to:G[u])
{
if(to==fa[u] || to==son[u]) continue;
dfss(to,to);
}
}
int lca(int u,int v)
{
while(tp[u]!=tp[v])
{
if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
u=fa[tp[u]];
}
return dep[u]<dep[v]?u:v;
}
int m;
struct node
{
int u,v,l;
bool operator < (const node &oth) const{
return dep[l]>dep[oth.l];
}
}q[maxn];
int res[maxn],cnt;
int c[maxn];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int v)
{
while(x<=n)
{
c[x]+=v;
x+=lowbit(x);
}
}
int Query(int x)
{
// cerr<<x<<endl;
x=min(x,n);
int ans=0;
while(x)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int query(int u,int v)
{
int ans=0;
while(tp[u]!=tp[v]) {
if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
ans+=Query(dfn[u])-Query(dfn[tp[u]]-1);
u=fa[tp[u]];
}
if(dep[u]<dep[v]) swap(u,v);
ans+=Query(dfn[u])-Query(dfn[v]-1);
return ans;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y; scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1); dfss(1,1);
// printf("%d\n",dfn[0]);
// return 0;
// for(int i=1;i<=n;i++) printf("%d %d\n",tp[i],dep[i]);
// return 0;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].u,&q[i].v);
q[i].l=lca(q[i].u,q[i].v);
}
// return 0;
sort(q+1,q+m+1);
for(int i=1;i<=m;i++)
{
if(query(q[i].u,q[i].v)) continue;
res[++cnt]=q[i].l;
add(dfn[q[i].l],1);
// for(int i=1;i<=n;i++) printf("%d ",c[i]);
// printf("\n");
}
printf("%d\n",cnt);
for(int i=1;i<=cnt;i++) printf("%d ",res[i]);
return 0;
}
Gym - 101741D
这道题目第一个观察很重要:如果 t i < t j t_i < t_j ti<tj且 a i < a j a_i<a_j ai<aj 那么 i i i就不会对答案造成影响,这样我们删除这样的点后,得到的就是t递增,a递减的数列
有了这样的性质,我们可以设 f [ i ] f[i] f[i]表示把前 i i i个人都运上去,并回到初始位置的最短时间
发现转移如下
f
[
i
]
=
m
i
n
(
m
a
x
(
t
[
i
]
,
f
[
j
]
)
+
2
∗
a
[
j
+
1
]
)
f[i]=min(max(t[i],f[j])+2*a[j+1])
f[i]=min(max(t[i],f[j])+2∗a[j+1])
然后我们可以讨论ti和fj的分界点,显然这个分界点是单调的,可以用一个指针完成
后面的min也可以用单调队列实现
时间复杂度 O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const ll inf=1e18;
int n,t[maxn],a[maxn];
ll f[maxn];
/*
struct seg
{
int tr[maxn<<2],tag[maxn<<2];
void build(int now,int l,int r)
{
tr[now]=inf;
if(l==r) return;
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
}
void pushdown(int now,int l,int r)
{
if(!tag[now]) return;
int mid=l+r>>1;
int lson=now<<1,rson=now<<1|1;
tr[lson]=max(tr[lson],tag[now]);
tr[rson]=max(tr[rson],tag[now]);
tag[lson]=max(tag[lson],tag[now]);
tag[rson]=max(tag[rson],tag[now]);
tag[now]=0;
}
void change(int now,int l,int r,int L,int R,int v)
{
if(l>=L && r<=R)
{
tag[now]=max(tag[now],v);
tr[now]=max(tr[now],v);
return;
}
int mid=l+r>>1;
if(L<=mid) change(now<<1,l,mid,L,R,v);
if(mid<R) change(now<<1|1,mid+1,r,L,R,v);
}
int query(int now,int l,int r,int L,int R)
{
if(l>=L && r<=R) return tr[now];
int mid=l+r>>1;
pushdown(now,l,r);
if(R<=mid) return query(now<<1,l,mid,L,R);
if(mid<L) return query(now<<1|1,mid+1,r,L,R);
return max(query(now<<1,l,mid,L,R),query(now<<1|1,mid+1,r,L,R));
}
int ask(int now,int l,int r,int L,int R)
{
if(l>=L && r<=R) return tr[now];
int mid=l+r>>1;
if(R<=mid) return ask(now<<1,l,mid,L,R);
if(mid<L) return ask(now<<1|1,mid+1,r,L,R);
return min(ask(now<<1,l,mid,L,R),ask(now<<1|1,mid+1,r,L,R));
}
}A,B;
int get_pos(int L,int R,int v)
{
int ans=0;
while(L<=R)
{
int mid=L+R>>1;
if(f[mid]<=v) ans=mid,L=mid+1;
else R=mid-1;
}
return ans;
}*/
pair <ll,int> q[maxn];
int l,r,cnt;
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(scanf("%d",&n)!=EOF)
{
l=1,r=0;
for(int i=1;i<=n;i++)
{
int x,y; scanf("%d%d",&x,&y);
while(l<=r && q[r].second<y)
r--;
q[++r]=make_pair(x,y);
// scanf("%d%d",&t[i],&a[i]);
}
for(int i=1;i<=n;i++) f[i]=inf;
cnt=0;
for(int i=1;i<=r;i++)
{
t[++cnt]=q[i].first;
a[cnt]=q[i].second;
}
l=1,r=0;
int pos=0;
for(int i=1;i<=cnt;i++)
{
// cerr<<f[i];
while(pos<=cnt && f[pos]<=t[i]) ++pos;
while(l<=r && q[l].second<pos) l++;
if(pos<=cnt && pos<=i)
f[i]=min(f[i],t[i]+2ll*a[pos]);
// cerr<<"["<<f[i]<<"]";
if(l<=r) f[i]=min(f[i],q[l].first);
while(l<=r && q[r].first>=f[i]+2*a[i+1] && i!=cnt)
r--;
q[++r]=make_pair(f[i]+2ll*a[i+1],i);
// cerr<<" "<<f[i]<<endl;
}
printf("%lld\n",f[cnt]);
/*B.build(1,0,n);
for(int i=1;i<=n;i++)
{
int pos=get_pos(1,i-1,t[i]);
A.change(1,0,n,0,i,a[i]);
f[i]=min(f[i],t[i]+2*A.query(1,0,n,0,pos));
f[i]=min(f[i],B.ask(1,0,n,pos+1,i-1));
B.upd(1,0,n,)
}*/
}
return 0;
}
Gym - 101741J
我们考虑使用类似线段树的方式去维护,每个点维护从mid开始向左和向右的一段的连续的背包的值,然后两段之间可以dp合并,查询的时候看中点是哪个,然后合并左右两段,否则递归到左右处理就好
时空复杂度 O ( n m l o g n ) O(nmlogn) O(nmlogn)
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+5;
const int mod=1e9+7;
int n,m,a[maxn],q;
struct seg
{
int v[20];
void clear()
{
v[0]=1;
for(int i=1;i<20;i++)
v[i]=0;
}
}tr[maxn<<5],tmp;
int cnt,st[maxn<<2];
int add(int x,int y)
{
if(x+y>=mod) return x+y-mod;
return x+y;
}
int Add(int x,int y)
{
if(x+y>=m) return x+y-m;
return x+y;
}
seg add(seg x,int val)
{
seg res=x;
// printf("[%d]\n",x.v[0]);
for(int i=0;i<m;i++)
if(x.v[i]) res.v[Add(i,val)]=add(res.v[Add(i,val)],x.v[i]);
return res;
}
void build(int now,int l,int r)
{
// cerr<<now<<" "<<l<<" "<<r<<endl;
cnt++; st[now]=cnt;
tmp.clear();
int mid=l+r>>1;
tmp.v[a[mid]]++;
tr[cnt]=tmp;
if(l==r) return;
for(int i=mid-1;i>=l;i--)
{
cnt++;
tmp=add(tmp,a[i]);
tr[cnt]=tmp;
}
tmp.clear();
for(int i=mid+1;i<=r;i++)
{
cnt++;
tmp=add(tmp,a[i]);
// if(cnt==6) printf("[%d %d ]\n",tmp.v[1],a[i]);
tr[cnt]=tmp;
}
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
}
int merge(seg x,seg y)
{
seg res;
res.clear(); res.v[0]--;
// cerr<<x.v[5]<<" "<<y.v[1]<<endl;
for(int i=0;i<m;i++)
if(x.v[i])
for(int j=0;j<m;j++)
if(y.v[j]) res.v[Add(i,j)]=add(res.v[Add(i,j)],1ll*x.v[i]*y.v[j]%mod);
return res.v[0];
}
int query(int now,int l,int r,int L,int R)
{
int mid=l+r>>1;
if(L<=mid && mid<R)
{
// cerr<<now<<" "<<l<<" "<<r<<endl;
int lenl=mid-L+1,lenr=R-mid;
int idl=st[now]+lenl-1,idr=st[now]+mid-l+lenr;
// cerr<<st[now]<<" "<<idr<<" "<<lenr<<endl;
// cerr<<tr[idr].v[0]<<" "<<tr[idr].v[1]<<endl;
return merge(tr[idl],tr[idr]);
}
if(R<=mid) return query(now<<1,l,mid,L,R);
return query(now<<1|1,mid+1,r,L,R);
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]%=m;
build(1,1,n);
// return 0;
scanf("%d",&q);
while(q--)
{
int l,r; scanf("%d%d",&l,&r);
if(l==r)
{
if(a[l]) printf("1\n");
else printf("2\n");
}
else printf("%d\n",query(1,1,n,l,r));
}
return 0;
}