郑州集训题目合集
Day1
NOIP 模拟赛 1
A
原来的假了。
感谢 @Kenma 提供 Hack ,我又补了一组:
input:
2
4
aaaa
abcb
bbbb
4
aaaa
aaaa
bbbb
ans:
0
1
原题数据极水,希望不要再被 Hack。
性质:答案只能是 \(0,1,3\)。
令 \(i+1=j\),则答案不可能超过 \(3\)。
尝试贪心地构造更小的答案。注意到 \(a_1\) 是必选的。首先扫一遍 \(b\),找到不等于 \(a_1\) 的第一个位置(若没有就令 \(i=1\)),接着扫 \(c\),若 \(c_{j+1}\ne a_1\land c_{j+1}\ne b_{i+1}\),就更新一次答案。处理出所有字符第一次出现在 \(b\) 中的位置 \(pre\)。则若字符不等于 \(a_1\),则令其等于 \(i+1\)。然后找 \(c_{j+1}\)。总体时间复杂度 \(O(n|\sum|)\)。
code
#include<iostream>
using namespace std;
typedef unsigned long long ull;
constexpr int N=1e5+10;
int t,n,ans,pre[N];
char a[N],b[N],c[N];
int main(){
freopen("lcp.in","r",stdin);
freopen("lcp.out","w",stdout);
scanf("%d",&t);
while(t--){
scanf("%d %s %s %s",&n,a+1,b+1,c+1);
int i=1,j=0;
ans=3;
for(int k=1;k<=n;k++)
if(!pre[b[k]-'a'])
pre[b[k]-'a']=k;
for(int k=2;k<=n;k++)
if(b[k]!=a[1]){
i=k;
break;
}
for(int k=i+2;k<=n;k++)
if(c[k]!=b[i+1]&&c[k]!=a[i+1]){
ans=min(ans,(a[1]==b[i+1])+(b[i+1]==c[j+1])+(c[j+1]==a[1]));
break;
}
for(int ch=0;ch<26;ch++){
if(ch+'a'==a[1]||!pre[ch]) continue;
i=pre[ch]-1,j=i+1;
for(int k=i+2;k<=n;k++)
if(c[k]!=b[i+1]){
j=k-1;
if(c[k]!=a[1]) break;
}
ans=min(ans,(a[1]==b[i+1])+(b[i+1]==c[j+1])+(c[j+1]==a[1]));
}
printf("%d\n",ans);
}
return 0;
}
B
容易发现,以 \(a_i\) 为第一关键字升序,\(b_i\) 为第二关键字降序排序可得答案。这样算出的答案是最优的。
朴素计算的复杂度是 \(O(n^2)\),尝试优化。对于 \(j<i\) 的部分,显然 \(a_j\le a_i\) 恒成立。对于 \(j>i\),分为 \(a_i=a_j\) 和 \(a_i<a_j\) 两种。可以预处理 \(nxt\) 数组表示当前相同数连续段末尾位置辅助计算。总体时间复杂度 \(O(n\log n)\)。瓶颈在于排序。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=1e6+10;
struct node{
int a,b;
friend bool operator<(const node &x,const node &y){
return x.a==y.a?x.b>y.b:x.a<y.a;
}
}p[N];
int n,nxt[N];
ll sum[N],sum2[N];
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
read(n);
for(int i=1;i<=n;i++)
read(p[i].a,p[i].b);
sort(p+1,p+1+n);
for(int i=n;i;i--)
if(p[i].a==p[i+1].a) nxt[i]=nxt[i+1];
else nxt[i]=i;
ll res=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+p[i].b;
sum2[i]=sum2[i-1]+(ll)p[i].b*p[i].b;
}
for(int i=1;i<=n;i++){
res+=(ll)(i-1)*p[i].b-sum[i-1];
int j=nxt[i];
if(i!=j)
res+=(ll)(j-i)*p[i].b*p[i].b-2ll*p[i].b*(sum[j]-sum[i])+sum2[j]-sum2[i];
res+=(ll)(n-j)*p[i].b-(sum[n]-sum[j]);
}
printf("%lld\n",res);
return 0;
}
C
史。
D
这个拆贡献很好啊,来不及写了就先记一下思路。
可以将 \(x,y\) 双方贡献拆成一方的贡献。
-
若 \(x\) 身份是 \(0\),贡献为 \(a_x^2+a_x\cdot a_y\);
-
若 \(x\) 身份是 \(1\),贡献为 \(a_x^2-a_x\cdot a_y\)。
只考虑互相指认的情况。显然有解当且仅当互相指认关系构成二分图。黑白染色即可求出这一部分的贡献。
对于连出的若干连通分量,做一个背包计算答案。
Day2
线段树
算术天才⑨与等差数列
绝世勾巴题。判断一个区间能重排成公差为 \(k\) 的等差数列的条件是:
-
区间的最小值作为首项,最大值作为末项构造出的公差 \(d\) 等于 \(k\);
-
区间没有重复的值;
-
区间所有相邻两项之差的绝对值的 \(\gcd\) 等于 \(k\)。
保证区间无相同数可以维护每个数的前驱,保证区间前驱最大值小于区间左端点即可。可以用 std::map 和 std::set 辅助维护。
原题数据极水,通过后可以尝试 Hack,注意:Hack 数据有一点小问题,询问区间可能会有 \(l>r\) 的情况。遇到这种情况请交换 \(l,r\)。
code
#include<cstdio>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=3e5+10;
map<int,set<int>> mp;
struct node{
int maxn,minn,maxpre;
inline friend node operator+(const node &x,const node &y){
if(x.maxn==-1) return y;
if(y.maxn==-1) return x;
node res;
res.maxn=max(x.maxn,y.maxn);
res.minn=min(x.minn,y.minn);
res.maxpre=max(x.maxpre,y.maxpre);
return res;
}
}tree1[N<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
int tree2[N<<2],n,m,P;
inline void modify(int x,int k){
int old=tree1[x+P].maxn;
auto it=mp[old].upper_bound(x),it1=it;
if(it!=mp[old].end()){
int i=*it+P;
int pre=--it==mp[old].begin()?0:*--it;
tree1[i].maxpre=pre;
for(i>>=1;i;i>>=1)
tree1[i].maxpre=max(tree1[ls(i)].maxpre,tree1[rs(i)].maxpre);
}
mp[old].erase(--it1);
it=mp[k].upper_bound(x);
if(it!=mp[k].end()){
int i=*it+P;
tree1[i].maxpre=x;
for(i>>=1;i;i>>=1)
tree1[i].maxpre=max(tree1[ls(i)].maxpre,tree1[rs(i)].maxpre);
}
it=mp[k].insert(x).first;
old=tree1[x+P].maxn;
tree1[x+P]={k,k,it==mp[k].begin()?0:*--it};
for(int i=(x+P)>>1;i;i>>=1)
tree1[i]=tree1[ls(i)]+tree1[rs(i)];
if(x<n){
tree2[x+P+1]+=old;
tree2[x+P+1]-=k;
for(int i=(x+P+1)>>1;i;i>>=1)
tree2[i]=__gcd(abs(tree2[ls(i)]),abs(tree2[rs(i)]));
}
tree2[x+P]-=old;
tree2[x+P]+=k;
for(int i=(x+P)>>1;i;i>>=1)
tree2[i]=__gcd(abs(tree2[ls(i)]),abs(tree2[rs(i)]));
}
inline node query1(int l,int r){
l+=P-1,r+=P+1;
node resl={-1},resr={-1};
while(l^1^r){
if(~l&1) resl=resl+tree1[l^1];
if(r&1) resr=tree1[r^1]+resr;
l>>=1,r>>=1;
}
return resl+resr;
}
inline int query2(int l,int r){
l+=P-1,r+=P+1;
int res=0;
while(l^1^r){
if(~l&1) res=__gcd(res,abs(tree2[l^1]));
if(r&1) res=__gcd(res,abs(tree2[r^1]));
l>>=1,r>>=1;
}
return res;
}
int main(){
read(n,m);
P=1;
while(P<=n+1) P<<=1;
for(int i=1,a,last;i<=n;i++){
read(a);
tree1[P+i]={a,a};
if(mp[a].empty()) tree1[P+i].maxpre=0;
else tree1[P+i].maxpre=*--mp[a].end();
mp[a].insert(i);
if(i>1) tree2[P+i]=a-last;
last=a;
}
for(int i=P-1;i;i--){
tree1[i]=tree1[ls(i)]+tree1[rs(i)];
tree2[i]=__gcd(abs(tree2[ls(i)]),abs(tree2[rs(i)]));
}
int op,x,y,k,cnt=0;
while(m--){
read(op);
if(op==1){
read(x,k);
x^=cnt,k^=cnt;
modify(x,k);
}
else{
read(x,y,k);
x^=cnt,y^=cnt,k^=cnt;
if(x>y) swap(x,y);
if(x==y){
printf("Yes\n"),++cnt;
continue;
}
node u=query1(x,y);
if(k==0){
if(u.maxn==u.minn) printf("Yes\n"),++cnt;
else printf("No\n");
continue;
}
int d=query2(x+1,y);
if(u.maxn-u.minn==(ll)(y-x)*k&&d==k&&u.maxpre<x)
printf("Yes\n"),++cnt;
else printf("No\n");
}
}
return 0;
}
【模板】三维偏序(陌上花开)
自学了一手 cdq 分治。分治过程中的排序可以直接利用分治结构进行归并。
code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=2e5+10;
struct node{
int a,b,c,val,ans;
bool operator==(const node &x)const{
return a==x.a&&b==x.b&&c==x.c;
}
}p[N],temp[N];
bool cmpa(const node &x,const node &y){
return x.a==y.a?(x.b==y.b?x.c<y.c:x.b<y.b):x.a<y.a;
}
bool cmpb(const node &x,const node &y){
return x.b==y.b?x.c<y.c:x.b<y.b;
}
int n,k,tot,ans[N],tree[N];
#define lowbit(x) (x&-x)
void modify(int x,int c){
for(;x<=k;x+=lowbit(x)) tree[x]+=c;
}
int query(int x){
int res=0;
for(;x;x^=lowbit(x)) res+=tree[x];
return res;
}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
int x=l,y=mid+1;
while(y<=r){
while(x<=mid&&p[x].b<=p[y].b){
modify(p[x].c,p[x].val);
++x;
}
p[y].ans+=query(p[y].c);
++y;
}
for(int i=l;i<x;i++) modify(p[i].c,-p[i].val);
x=l,y=mid+1;
int cnt=l-1;
while(x<=mid&&y<=r){
if(cmpb(p[x],p[y])) temp[++cnt]=p[x++];
else temp[++cnt]=p[y++];
}
while(x<=mid) temp[++cnt]=p[x++];
while(y<=r) temp[++cnt]=p[y++];
for(int i=l;i<=r;i++) p[i]=temp[i];
}
int main(){
read(n,k);
for(int i=1;i<=n;i++)
read(p[i].a,p[i].b,p[i].c),p[i].val=1;
sort(p+1,p+1+n,cmpa);
for(int i=1;i<=n;i++){
if(p[i]==p[tot]) ++p[tot].val;
else p[++tot]=p[i];
}
cdq(1,tot);
for(int i=1;i<=tot;i++)
ans[p[i].ans+p[i].val-1]+=p[i].val;
for(int i=0;i<n;i++)
printf("%d\n",ans[i]);
return 0;
}
Day3
NOIP 模拟赛 2
A
容易发现,同一个连通块的答案相同,记 \(sum\) 为所有 \(1\) 的数量,\(size\) 为当前连通块大小,该连通块的答案就是 \(size/(sum+1)\)。枚举所有 \(0\),模拟加入点后合并连通块,注意去重。时间复杂度 \(O(nm)\)。
code
#include<iostream>
using namespace std;
constexpr int N=2010;
int n,m,s[N][N],fa[N*N],siz[N*N],sum;
bool vis[N][N];
void read(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%1d",&s[i][j]);
}
inline int id(int x,int y){return (x-1)*m+y;}
void debug(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
// printf("%d ",s[i][j]);
printf("%d ",siz[fa[id(i,j)]]);
}
printf("\n");
}
}
constexpr int fx[]={1,0,-1,0},fy[]={0,1,0,-1};
struct node{int x,y;}q[N*N];
int l,r;
void bfs(int sx,int sy){
l=1,r=0;
q[++r]={sx,sy};
int root=id(sx,sy);
fa[root]=root;
siz[root]=1;
vis[sx][sy]=1;
while(l<=r){
node u=q[l++];
for(int i=0;i<4;i++){
int x=u.x+fx[i],y=u.y+fy[i];
if(x<1||y<1||x>n||y>m||!s[x][y]||vis[x][y])
continue;
vis[x][y]=1;
q[++r]={x,y};
++siz[root];
fa[id(x,y)]=root;
}
}
}
void get_size(){//获取不加入时的连通块大小
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(s[i][j]&&!vis[i][j])
bfs(i,j);
sum+=s[i][j];
}
}
int temp[5],cnt;
inline bool find(int x){
for(int i=1;i<=cnt;i++)
if(temp[i]==x) return 1;
return 0;
}
void solve(){
get_size();
double ans=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(s[i][j]) continue;
int sumk=1;
cnt=0;
for(int k=0;k<4;k++){
int x=i+fx[k],y=j+fy[k];
if(x<1||y<1||x>n||y>m)
continue;
int belong=fa[id(x,y)];
if(!s[x][y]||find(belong))
continue;
sumk+=siz[belong];
temp[++cnt]=belong;
}
ans=min(ans,(double)sumk/(sum+1));
}
printf("%.9lf\n",ans);
}
void solve_sub5(){//特殊性质 A,显然可以自己形成一个大小为 1 的连通块
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum+=s[i][j];
printf("%.9lf\n",1.0/(sum+1));
}
int main(){
freopen("plague.in","r",stdin);
freopen("plague.out","w",stdout);
read();
solve();
return 0;
}
B
我以为写了个 \(O(nq\log n)\),但好像是调和级数就变成 \(O(q\log^2n)\) 了,不太清楚。一个重要转化是从中间往两边跳变为从两边往中间跳。
code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=1e6+10;
int n,q,x[N],maxn;
vector<int> from[N];
struct query{
int val,id;
bool operator<(const query &x)const{
return val<x.val;
}
}k[N];
void solvesub1(){
for(int c=1;c<=q;c++){
read(k[c].val);
if(k[c].val<maxn){
printf("-1 ");
continue;
}
int ans=2e9;
for(int mid=1;mid<=n;mid++){
int l=mid,r=mid,cnt=0;
while(l>1||r<n){
if(l>1) l=lower_bound(x+1,x+1+n,x[l]-k[c].val)-x;
if(r<n) r=upper_bound(x+1,x+1+n,x[r]+k[c].val)-x-1;
++cnt;
}
if(ans>cnt) ans=cnt,from[c].clear(),from[c].push_back(mid);
else if(ans==cnt) from[c].push_back(mid);
}
printf("%d ",ans);
}
printf("\n");
// for(int i=1;i<=q;i++){
// printf("%d:",k[i]);
// for(int x:from[i])
// printf("%d ",x);
// printf("\n");
// }
}
int ans[N],cnt;
void solve2(){
for(int i=1;i<=q;i++) read(k[i].val),k[i].id=i;
sort(k+1,k+1+q);
int d=maxn;
for(int l=1,r=n;l<r;){
l=upper_bound(x+1,x+1+n,x[l]+d)-x-1;
r=lower_bound(x+1,x+1+n,x[r]-d)-x;
++cnt;
}
for(int i=1;i<=q;i++){
if(k[i].val<maxn){
ans[k[i].id]=-1;
continue;
}
if(k[i].val<=d){
ans[k[i].id]=cnt;
continue;
}
d=k[i].val;
cnt=0;
for(int l=1,r=n;l<r;){
l=upper_bound(x+1,x+1+n,x[l]+d)-x-1;
r=lower_bound(x+1,x+1+n,x[r]-d)-x;
++cnt;
}
ans[k[i].id]=cnt;
}
for(int i=1;i<=q;i++)
printf("%d ",ans[i]);
}
int main(){
freopen("beacon.in","r",stdin);
freopen("beacon.out","w",stdout);
read(n,q);
for(int i=1;i<=n;i++) read(x[i]);
for(int i=2;i<=n;i++) maxn=max(maxn,x[i]-x[i-1]);
solve2();
return 0;
}
C
史。
D
史。
Day4
树上问题
[SDOI2016] 游戏
发现修改操作类似插入线段,考虑使用树链剖分+李超线段树维护,设 \(dis_u\) 表示 \(u\) 到根节点的距离,这里显然满足一条重链上 \(dis\) 单调递增,令 \(x=dis_x\) 即可用李超线段树维护,设 \(s,t\) 的 lca 为 \(x\),将一条路径拆成 \(s\to x\) 和 \(x\to t\) 两部分。
设 \(p\) 为路径上的一个点。
-
对于 \(s\to x\),这一段为 \(a\times (dis_p-dis_s)+b\),则插入线段 \(y=-ax+a\times dis_s+b\)。
-
对于 \(x\to t\),这一段为 \(a\times(dis_s+dis_p-2\times dis_x)+b\),则插入线段 \(y=ax+a\times(dis_s-2\times dis_x)+b\)。
查询最小值比较平凡。
code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=1e5+10;
constexpr ll inf=123456789123456789ll;
struct edge{int v,w;};
vector<edge> e[N];
int n,m;
int siz[N],top[N],dfncnt,dfn[N],son[N],fa[N],dep[N],idfn[N];
ll dis[N];
void dfs1(int u,int f){
fa[u]=f;
siz[u]=1;
dep[u]=dep[f]+1;
for(auto to:e[u]){
if(to.v==f) continue;
dis[to.v]=dis[u]+to.w;
dfs1(to.v,u);
siz[u]+=siz[to.v];
if(siz[to.v]>siz[son[u]])
son[u]=to.v;
}
}
void dfs2(int u,int topf){
dfn[u]=++dfncnt;
idfn[dfncnt]=u;
top[u]=topf;
if(son[u]) dfs2(son[u],topf);
for(auto to:e[u]){
if(to.v==fa[u]||to.v==son[u]) continue;
dfs2(to.v,to.v);
}
}
struct line{
ll k,b;
inline ll Y(int x){return dis[idfn[x]]*k+b;}
};
#define ls (u<<1)
#define rs (u<<1|1)
struct node{
line L;
ll minn;
node():L({0,inf}),minn(inf){}
}tree[N<<2];
void push_up(int u){
tree[u].minn=min({tree[u].minn,tree[ls].minn,tree[rs].minn});
}
void modify(int u,int l,int r,int x,int y,line k){
if(x<=l&&y>=r){
ll ly1=tree[u].L.Y(l),ry1=tree[u].L.Y(r);
ll ly2=k.Y(l),ry2=k.Y(r);
if(ly2<ly1&&ry2<ry1){
tree[u].L=k;
tree[u].minn=min({tree[u].minn,ly2,ry2});
}
else if(ly2<ly1||ry2<ry1){
int mid=(l+r)>>1;
if(k.Y(mid)<tree[u].L.Y(mid)) swap(k,tree[u].L);
if(k.Y(l)<tree[u].L.Y(l))
modify(ls,l,mid,x,y,k);
else modify(rs,mid+1,r,x,y,k);
tree[u].minn=min({tree[u].minn,tree[u].L.Y(l),tree[u].L.Y(r)});
push_up(u);
}
return;
}
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,y,k);
if(y>mid) modify(rs,mid+1,r,x,y,k);
push_up(u);
}
ll query(int u,int l,int r,int x,int y){
if(x<=l&&y>=r) return tree[u].minn;
int ql=max(l,x),qr=min(r,y);
ll res=min(tree[u].L.Y(ql),tree[u].L.Y(qr));
int mid=(l+r)>>1;
if(x<=mid) res=min(res,query(ls,l,mid,x,y));
if(y>mid) res=min(res,query(rs,mid+1,r,x,y));
return res;
}
void change(int x,int y,line k){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,1,n,dfn[top[x]],dfn[x],k);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
modify(1,1,n,dfn[x],dfn[y],k);
}
ll ask(int x,int y){
ll res=inf;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=min(res,query(1,1,n,dfn[top[x]],dfn[x]));
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
res=min(res,query(1,1,n,dfn[x],dfn[y]));
return res;
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int main(){
read(n,m);
for(int i=1,u,v,w;i<n;i++){
read(u,v,w);
e[u].push_back({v,w});
e[v].push_back({u,w});
}
dfs1(1,0);
dfs2(1,1);
int op,s,t,a,b;
while(m--){
read(op,s,t);
if(op==1){
read(a,b);
int x=lca(s,t);
change(s,x,{-a,a*dis[s]+b});
change(x,t,{a,a*(dis[s]-2*dis[x])+b});
}
else printf("%lld\n",ask(s,t));
}
return 0;
}
Day5
NOIP 模拟赛 3
C
树上莫队。
Day6
NOIP 模拟赛 4
A
注意到最多操作 \(40\) 次,直接暴力上个二分优化即可。
code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=5e5+10;
int n,r,tot,ans[N],a[N],q[N],ql,qr,cnt;
inline bool check(int mid){
return mid-r<=q[ql]&&mid+r>=q[qr];
}
int main(){
freopen("mine.in","r",stdin);
freopen("mine.out","w",stdout);
read(n,r);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+1+n);
while(cnt<n){//40
ql=1,qr=0;
int maxsiz=0,resl=1,resr=1;
++tot;
for(int i=1;i<=n-cnt;i++){
q[++qr]=a[i];
while(q[ql]+2*r<q[qr]) ++ql;
if(qr-ql+1>maxsiz){
maxsiz=qr-ql+1;
int pl=a[ql]-r,pr=a[qr]+r;
while(pl<=pr){
int mid=(pl+pr)>>1;
if(check(mid)) pr=mid-1;
else pl=mid+1;
}
ans[tot]=pl;
resl=ql,resr=qr;
}
}
for(int i=1,j=0;i<=n-cnt;i++){
if(i>=resl&&i<=resr) continue;
a[++j]=a[i];
}
cnt+=maxsiz;
}
printf("%d\n",tot);
for(int i=1;i<=tot;i++)
printf("%d ",ans[i]);
return 0;
}
B
bitset 魔怔题。
C
不会。
D
史。
Day7
出去玩了。
Day8
没写题。
Day9
数论
【UR #1】缩进优化
额被骗了,根本就不是整除分块啊。
题意:给出序列 \(a_i\),求 \(x\) 使得最小化
首先推式子,原式变为
则只需最大化第二个 \(\sum\) 后那一部分。
直接枚举 \(x\) 并求答案,由于 \(x\) 在分母上,所以复杂度是调和级数 \(O(n\log n)\),开一个桶并维护前缀和即可。
code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
typedef long long ll;
constexpr int N=1e6+10;
int n,a[N],b[N],sum[N];
ll ans,res,suma;
int main(){
read(n);
for(int i=1;i<=n;i++) read(a[i]),++b[a[i]],suma+=a[i];
sum[0]+=b[0];
for(int i=1;i<=1e6;i++) sum[i]=sum[i-1]+b[i];
ans=1e18;
for(int i=1;i<=1e6;i++){
res=0;
int l=i,r=2*i-1;
ll cnt=1;
while(1){
if(r>1e6){
r=1e6;
res+=cnt*(sum[r]-sum[l-1]);
break;
}
res+=cnt*(sum[r]-sum[l-1]);
++cnt;
l+=i,r+=i;
}
ans=min(ans,suma-res*(i-1));
}
printf("%lld\n",ans);
return 0;
}
Day10
NOIP 模拟赛 6
A
唐题。
B
唐题。
C
容易想到二分,但是觉得太屎没做分讨。有类似题目。
D
根号分治解决独立集问题。
无向图三元环数量最多 \(O(m\sqrt m)\) 个。
Day11
贪心
Difficult Mountain
结论:按照 \(\max(s_i,a_i)\) 第一关键字,\(s_i\) 第二关键字升序排序,能选则选。证明。
code
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &x){
bool f=0;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=5e5+10;
int n,d,ans;
struct node{
int s,a;
bool operator<(const node &x)const{
if(max(s,a)==max(x.s,x.a))
return s<x.s;
return max(s,a)<max(x.s,x.a);
}
}p[N];
int main(){
read(n,d);
for(int i=1;i<=n;i++)
read(p[i].s,p[i].a);
sort(p+1,p+1+n);
for(int i=1;i<=n;i++){
if(p[i].s<d) continue;
++ans;
d=max(d,p[i].a);
}
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号