2025.10.31写题
P5309 [Ynoi2011] 初始化
省选/NOI-
操作一y+x,y+2*x...到大于n,加z
操作二求【l,r】的和
分块
对于x>根号n,考虑暴力,每次操作复杂度根号n
对于x<=根号n,预处理答案
由于y<x不难想到n在取模x的情况下被分块,pre[i][x]表示跨度为x的情况下1~i的加成和,suf[i][x]表示在取模x的情况下i~x的加成和
x<=根号n的情况答案可以分为,原块的和于暴力加上的和,求这个复杂度为根号n,与小于根号n的操作加成和,复杂度为根号n
AcCode:
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define endl '\n'
const int N=2e5+10;
const int M=450;
const ll mod=1e9+7;
int n,m,len,bnum,b[N],bl[M],br[M];
ll a[N],pre[M][M],suf[M][M],sum[M],x,y,z,op,l,r;
void init()
{len=sqrt(n);bnum=(n+len-1)/len;for(int i=1;i<=n;i++){b[i]=(i-1)/len+1;sum[b[i]]+=a[i];}for(int i=1;i<=bnum;i++){bl[i]=(i-1)*len+1;br[i]=min(i*len,n);}
}
ll query(int l,int r)
{ll res=0;if(b[l]==b[r]){for(int i=l;i<=r;i++)res+=a[i];}else{for(int i=b[l]+1;i<=b[r]-1;i++)res+=sum[i];for(int i=l;i<=br[b[l]];i++)res+=a[i];for(int i=bl[b[r]];i<=r;i++)res+=a[i];}for(int i=1;i<=len;i++){int bx=(l-1)/i+1,by=(r-1)/i+1;if(bx==by)res+=pre[i][(r-1)%i+1]-pre[i][(l-1)%i];else{ll cnt=by-bx-1;res+=suf[i][(l-1)%i+1];res+=pre[i][(r-1)%i+1];res+=cnt*pre[i][i];}}return res%mod;
}
int main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];init();for(int i=1;i<=m;i++){cin>>op;if(op==1){cin>>x>>y>>z;if(x<=len){for(int j=y;j<=x;j++)pre[x][j]+=z;for(int j=y;j>=1;j--)suf[x][j]+=z;}else{for(int j=y;j<=n;j+=x){a[j]+=z;sum[b[j]]+=z;}}}else {cin>>l>>r;cout<<query(l,r)<<endl;}}return 0;
}和ex的一道题,分块居然卡常,时限只有500ms,不能随时取模,不然会被卡
P3645 [APIO2015] 雅加达的摩天楼
省选/NOI-
有n个大楼,编号0~n-1,有m个狗子,编号0~m-1
每只狗子有两个参数,idx表示狗子的初始大楼,jump表示狗子的跳跃能力
狗子在i位置,可以来到 i - jump 或 i + jump,向左向右自由跳跃,但不能越界
0号狗子有消息希望传给1号狗子,所有狗子都可帮忙,返回至少传送几次,无法送达打印-1
一眼bfs,考虑状态数是否合法
对于jump<=根号n,每个位置都有n*根号n个状态
对于jump>根号n,最多跳根号n回,状态最多m*根号n
n,m<=30000时,完全合法
AcCode:
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define endl '\n'
const int N=3e4+10;
bitset<N>vis[N];
int n,m,ed;
vector<int>edge[N];
struct node{int pos,p,ans;
};
int main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m;queue<node>q;for(int i=1;i<=m;i++){int b,p;cin>>b>>p;edge[b].push_back(p);if(i==1){q.push({b,p,0});vis[b][p]=true;}if(i==2)ed=b;}while(!q.empty()){auto [pos,p,ans]=q.front();q.pop();if(pos==ed){cout<<ans<<endl;return 0;}if(pos+p<=n-1&&!vis[pos+p][p]){vis[pos+p][p]=true;q.push({pos+p,p,ans+1});}if(pos-p>=0&&!vis[pos-p][p]){vis[pos-p][p]=true;q.push({pos-p,p,ans+1});}for(auto &d:edge[pos]){if(pos+d<=n-1&&!vis[pos+d][d]){vis[pos+d][d]=true;q.push({pos+d,d,ans+1});}if(pos-d>=0&&!vis[pos-d][d]){vis[pos-d][d]=true;q.push({pos-d,d,ans+1});}}}cout<<-1<<endl;return 0;
}CF786 C. Till I Collapse
2400
这是一个最小划分问题人当然让人
给定一个长度为n的数组arr,考虑如下问题的解
数组arr划分成若干段子数组,保证每段不同数字的种类 <= k,返回至少划分成几段
打印k = 1, 2, 3..n时,所有的答案
当k<=根号n,暴力,每次o(n),复杂度n*根号n
当k>根号n,每段段长至少为根号n,故答案一定是小于等于根号n,并且随k增大,段数减少,最多根号n个分界点,将这根号n个分界点找出来,复杂度n*根号n*log(根号n)
AcCode:
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define endl '\n'
const int N=1e5+10;
int n,len,a[N],ans[N];
bool vis[N];
int query(int k)
{int cnt=0,ans=0,start=1;for(int i=1;i<=n;i++){if(!vis[a[i]]){if(cnt==k){for(int j=start;j<i;j++)vis[a[j]]=false;start=i;cnt=1;ans++;}else cnt++;vis[a[i]]=true;}}for(int i=start;i<=n;i++)vis[a[i]]=false;return ans+1;
}
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];len=sqrt(n);for(int i=1;i<=n;i++){if(i<=len)ans[i]=query(i);else{int l=i,r=n+1;int chk=query(i);while(l+1<r){int mid=(l+r)>>1;if(query(mid)==chk)l=mid;else r=mid;}ans[i]=chk;i=l;}}for(int i=1;i<=n;i++)if(ans[i]==0)ans[i]=ans[i-1];for(int i=1;i<=n;i++)cout<<ans[i]<<" ";return 0;
}CF1039D You Are Given a Tree
2800
给你一棵树
一共有n个节点,给定n-1条边,所有节点连成一棵树
树的路径是指,从端点x到端点y的简单路径,k路径是指,路径的节点数正好为k
整棵树希望分解成尽量多的k路径,k路径的节点不能复用,所有k路径不要求包含所有点
打印k = 1, 2, 3..n时,k路径有最多有几条
跟上面一题是同一个问题,不过变成了在树上划分
对于一次路径长为k的划分,如何做到o(n)的复杂度呢?
考虑树形dp
在树上划分,贪心的来说,我更希望一条路径是曲折的,而不是一条向上的直线,如果有一个节点它有两颗子树与他划分正好能形成一条路径,那就直接划分,如果不划分,继续让其向上连,会影响其他子树连接,而且其形成时也是只加1,显然不优
我用mx1[x],mx2[x]表示x的子树中没有形成路径的最长路径和次长路径
当mx1[x]+mx2[x]+1>=k时,可以直接连接
单次查询o(n)
当k<=根号n,暴力,每次o(n),复杂度n*根号n
当k>根号n,每段段长至少为根号n,故答案一定是小于等于根号n,并且随k增大,段数减少,最多根号n个分界点,将这根号n个分界点找出来,复杂度n*根号n*log(根号n)
Accode:
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define endl '\n'
const int N=1e5+10;
vector<int>edge[N];
int n,len,cnt,f[N],mx1[N],mx2[N],ans[N],fa[N],dfn[N];
void dfs(int x)
{dfn[++cnt]=x;for(auto &y:edge[x]){if(y==fa[x])continue;fa[y]=x;dfs(y);}
}
int query(int k)
{int ans=0;for(int i=n,cur;i>=1;i--){cur=dfn[i];if(mx1[cur]+mx2[cur]+1>=k){ans++;f[cur]=0;}else f[cur]=mx1[cur]+1;if(f[cur]>mx1[fa[cur]])mx2[fa[cur]]=mx1[fa[cur]],mx1[fa[cur]]=f[cur];else if(f[cur]>mx2[fa[cur]])mx2[fa[cur]]=f[cur];}for(int i=1;i<=n;i++)f[i]=mx1[i]=mx2[i]=0;return ans;
}
int main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n-1;i++){int u,v;cin>>u>>v;edge[u].push_back(v);edge[v].push_back(u);}dfs(1);for(int i=1;i<=n;i++)ans[i]=-1;len=max(1,(int)sqrt(n*log2(n)));for(int i=1;i<=n;i++){if(i<=len)ans[i]=query(i);else{int l=i,r=n+1;int chk=query(i);while(l+1<r){int mid=(l+r)>>1;if(query(mid)==chk)l=mid;else r=mid;}ans[i]=chk;i=l;}}for(int i=1;i<=n;i++)if(ans[i]==-1)ans[i]=ans[i-1];for(int i=1;i<=n;i++)cout<<ans[i]<<endl;return 0;
}注意虽然给了7s时限,但要转化成欧拉序,以循环的形式跑递归,不然dfs递归开销大,会超时
