模拟赛2025
模拟赛 2025
10.24 NOIP 模拟赛 十一
100+100+50+100=350 pts,rk1.
比较抽象,又被 T1 创飞了,写了将近 3 小时。
T2 暴力拿满,HHHOJ 机子跑的太快了。
T1
给定一个自然数 \(N(N\le 10^6)\),找出一个 \(M\),使得 \(M > 0\) 且 \(M\) 是 \(N\) 的倍数,并且 \(M\) 的 \(10\) 进制表示只包含 \(0\) 或 \(1\)。求最小的 \(M\)。无解输出 \(-1\)。
原题 lg P2841,不过数据加强了一点,但不用输出 \(M \div N\)。
bfs,将模 \(N\) 余数相同的剪枝。
据说可以加强到 \(N\le 10^9\),但我不会。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define PSL pair<string,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n;
bool vis[N];
queue<PSL> q;
int main(){
IOS;cin>>n;
q.push({"1",1});
vis[1]=1;
while(!q.empty()){
PSL tp=q.front();q.pop();
if(!(tp.second*10%n)) cout<<tp.first+"0"<<"\n",exit(0);
if(!((tp.second*10+1)%n)) cout<<tp.first+"1"<<"\n",exit(0);
if(!vis[tp.second*10%n]){
q.push({tp.first+"0",tp.second*10%n});
vis[tp.second*10%n]=1;
}
if(!vis[(tp.second*10+1)%n]){
q.push({tp.first+"1",(tp.second*10+1)%n});
vis[(tp.second*10+1)%n]=1;
}
}
return 0;
}
T2
我们定义两个 \(01\) 串的相似值为:如果两个串在第 \(i\) 位是一样的,那么他们的相似值要加上 \(w_i\)。
现在给你 \(m\) 个长度为 \(n\) 的 \(01\) 串,然后有 \(q\) 个询问,每一次给定一个长度为 \(n\) 的 \(01\) 串,和一个值 \(k\),求在这 \(m\) 个串中,有多少个串和它的相似值不超过 \(k\)。
\(n \le 15,k \le 30,m\le 5\times 10^5\)。
设 \(f_{S,k}\) 表示询问为 \(S\) 时小于等于 \(k\) 的个数,暴力枚举。复杂度 \(\mathcal{O(2^{2n})}\) 居然能跑过。
正解:
题目代价可以转化为:将第 \(i\) 位取反的代价为 \(w_i\),问能在小于等于 \(k\) 的代价将其变成与 \(S\) 完全不同的串的数量。
设 \(f_{i,S,k}\) 表示第 \(1 \sim i\) 位与 \(S\) 完全不同,第 \(i+1 \sim n\) 位与 \(S\) 完全相同,且代价小于等于 \(k\) 的串的数量。
有转移:
初始状态 \(f_{0,S,k}=num_S\),其中 \(num_S\) 表示给定的 \(01\) 串为 \(S\) 的个数。
每次询问答案即为 \(f_{n,S,k}\)。复杂度 \(\mathcal{O(2^n n k + q)}\)
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=5e5+5,M=15+5,P=(1<<15)+5;
const ll INF=1ll<<60,mod=998244353;
int n,m,q,w[M],a[N],b[P],f[2][P][M<<1],num[P];
int main(){
IOS;cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=1;i<=m;i++){
for(int j=0;j<n;j++){
char ch;cin>>ch;
if(ch=='1') a[i]|=1<<j;
}
num[a[i]]++;
}
int U=(1<<n)-1;
for(int t=0;t<=U;t++){
for(int j=0;j<=30;j++) f[0][t][j]=num[t];
}
for(int i=1;i<=n;i++){
MS(f[i&1],0);
for(int t=0;t<=U;t++){
for(int j=0;j<=30;j++){
if(j>=w[i]) f[i&1][t][j]=f[(i&1)^1][t][j-w[i]];
f[i&1][t][j]+=f[(i&1)^1][t^(1<<(i-1))][j];
}
}
}
for(int i=1,x,k;i<=q;i++){
x=0;
for(int j=0;j<n;j++){
char ch;cin>>ch;
if(ch=='1') x|=1<<j;
}
cin>>k;
cout<<f[n&1][x][k]<<"\n";
}
return 0;
}
T3
咕咕咕。
T4
给你 \(n\) 个数,对于每个数 \(a_i\),判断是否存在一个 \(a_j\),使得 \(a_i \& a_j=0\),\(\&\) 表示按位与。
SOSDP 板子,等价于是否存在 \(a_j \subseteq U\setminus a_i)\),不知为何放 T4。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=(1<<20)+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,a[N];
bool f[N];
int main(){
IOS;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],f[a[i]]=1;
int U=(1<<20)-1;
for(int t=0;t<=U;t++){
for(int i=0;i<20;i++){
if(!((t>>i)&1)) continue;
f[t]|=f[t^(1<<i)];
}
}
for(int i=1;i<=n;i++) cout<<f[U^a[i]]<<" ";
cout<<"\n";
return 0;
}
10.25 NOIP 模拟赛 十二
90+0+0+0=90pts,rk9
被 T1 创飞,导致 T2 想出正解但没写完,糖丸了。
T1
\(n\) 个点,给定 \(a_i,b_i (2 \le i \le n)\),表示 \(i\) 向 \([a_i,i]\) 连权值为 \(1\) 的边,\([b_i,i]\) 向 \(i\) 连权值为 \(1\) 的边。
\(dis_{i,j}\) 表示 \(i\) 到 \(j\) 的最短路,求所有的 \(dis_{i,j}(1\le i,j \le n)\)。答案输出 \(dis_{i,j} \times (i+j)\) 的异或和。
\(n\le 6000\)
赛时想了一个假的做法,但拿了高达 90pts。
正解:
考虑枚举出发点 \(s\) ,设 \(f_i\) 表示 \(s\) 到 \(i\) 的最短路。有 \(f_s=0\)。
对于 \(i>s\),会发现最短路肯定是 \(s\) 向右一直跳到一个 \(j\),再向左跳若干步到达 \(i\)(可以不往左挑)。其他情况不是最优的。
对于 \(i<s\),最短路是由 \(s\) 一直向左跳到 \(i\);或是先向左跳到 \(j(i < j \le s)\),再跳到 \(k(k>s)\),最后跳到 \(i\)。
计算可以设 \(pos_w\) 表示所有的 \(i\) 满足 \(f_i = w\),它最小往左能跳到的位置。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
#define int ll
using namespace std;
const int N=6e3+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,a[N],b[N],pos[N],f[N],ans;
void solve(int s){
MS(pos,0);MS(f,0x3f);
f[s]=0;pos[0]=s;
for(int i=s+1,x=0;i<=n;i++){//往右跳
x++;
for(;x && pos[x-1]>=b[i];x--);
pos[f[i]=x+1]=i;
}
MS(pos,0x3f);
for(int i=n,x=INF;i;i--){//从 i 右边某个点跳过来
x=min(x+1,f[i]);
for(;x && pos[x-1]<=i;x--);
f[i]=min(f[i],x+1);
pos[f[i]]=min(pos[f[i]],a[i]);
}
for(int i=1;i<=n;i++) ans^=f[i]*(i+s);
}
signed main(){
IOS;cin>>n;
for(int i=2;i<=n;i++) cin>>a[i];
for(int i=2;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) solve(i);
cout<<ans<<"\n";
return 0;
}
T2
有⼀棵 \(n\) 个节点的树,每条边⻓度为 \(1\),颜⾊为⿊或⽩。可以执⾏若⼲次如下操作:
- 选择⼀条简单路径,反转路径上所有边的颜⾊。
对于某些边,要求在操作结束时为某⼀种颜⾊。给定每条边的初始颜⾊,求最⼩操作数,以及满⾜操作数最⼩时,最⼩的操作路径⻓度和。
可通过将目标颜色和当前颜色的转化,将问题转化为:给定若干个点的颜色,其余点的颜色可以任意取,求答案。
树形 dp,设 \(f_{i,0/1}\) 表示 \(i\) 到它父亲的这条边为 \(0/1\) 这种颜色的最优答案。
可以将与 \(i\) 这个点连的边合并成一条路径,两个单独的路径合并可以使操作次数减 \(1\)。分别考虑奇数条边与偶数条边的转移即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1e9,mod=998244353;
int n;
PII f[N][2];
vector<PII> to[N];
PII operator + (const PII xx,const PII yy){
return {xx.first+yy.first,xx.second+yy.second};
}
void dfs(int u,int fa,int fc){
PII q={0,0},p={INF,INF};//q 0,p 1
if(fc==1) f[u][0]={INF,INF};
if(fc==0) f[u][1]={INF,INF};
for(PII ttp:to[u]){
int v=ttp.first,c=ttp.second;
if(v==fa) continue;
dfs(v,u,c);
PII tp=p,tq=q;
p=min(tp+f[v][0],tq+f[v][1]);
q=min(tp+f[v][1],tq+f[v][0]);
}
if(fc==2 || fc==1) f[u][1]=min(PII{q.first+1,q.second+1},PII{p.first,p.second+1});
if(fc==2 || fc==0) f[u][0]=min(PII{p.first+1,p.second},q);
}
int main(){
IOS;cin>>n;
for(int i=1,u,v,c,d;i<n;i++){
cin>>u>>v>>c>>d;
c^=(d==1);//转换条件
if(d==2) c=2;
to[u].eb(v,c);
to[v].eb(u,c);
}
dfs(1,0,2);
cout<<f[1][0].first/2<<" "<<f[1][0].second<<"\n";
return 0;
}
T3
咕咕咕。
T4
给定⻓度为 \(n\) 的正整数序列 ,以及 \(q\) 个操作。每个操作是以下 \(2\) 种之⼀:
- \(1~~pos~~val\):将 \(a_{pos}\) 改为 \(val\)。
- \(2~~l~~r\): 询问如果从 \(a_{l,\dots,r}\) 中选择三个元素作为三⻆形三条边的⻓度,能够围出的三⻆形的最⼤周⻓是多少。请注意,每个元素最多被选⼀次。
暴力做法为将 \(a_{l,\dots,r}\) 部分从大到小排序后找到第一个 \(i\) 满足 \(a_i+a_{i+1}>=a_{i+2}\) 即为答案。
会发现不存在答案的情况的序列最长为斐波那契数列,最长只有 \(60\) 左右。因此只用保留前 \(60\) 个数即可。用线段树维护。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,q,a[N];
struct Tree{
int l,r,cnt[66],len;
#define l(p) t[p].l
#define r(p) t[p].r
#define cnt(p,x) t[p].cnt[x]
#define len(p) t[p].len
}t[N<<2];
int Cnt[66],Len,b[66];
void out(int p){
for(int i=1;i<=len(p);i++) cout<<cnt(p,i)<<" ";cout<<"\n";
}
void up(int p){
len(p)=1;
for(int l1=1,l2=1;len(p)<=60 && (l1<=len(p<<1) || l2<=len(p<<1|1));len(p)++){
if(cnt(p<<1,l1)>=cnt(p<<1|1,l2)) cnt(p,len(p))=cnt(p<<1,l1++);
else cnt(p,len(p))=cnt(p<<1|1,l2++);
}
len(p)--;
}
void merge(int p){
int LL=1;MS(b,0);
for(int l1=1,l2=1;LL<=60 && (l1<=Len || l2<=len(p));LL++){
if(Cnt[l1]>=cnt(p,l2)) b[LL]=Cnt[l1++];
else b[LL]=cnt(p,l2++);
}
Len=LL-1;MC(Cnt,b);
}
void Build(int p,int l,int r){
l(p)=l,r(p)=r;
if(l==r) return cnt(p,len(p)=1)=a[l],void();
int mid=(l+r)>>1;
Build(p<<1,l,mid);Build(p<<1|1,mid+1,r);
up(p);
}
void update(int p,int x,int d){
if(l(p)==r(p)) return cnt(p,1)=d,void();
int mid=(l(p)+r(p))>>1;
if(x<=mid) update(p<<1,x,d);
else update(p<<1|1,x,d);
up(p);
}
void query(int p,int l,int r){
if(l<=l(p) && r(p)<=r) return merge(p),void();
int mid=(l(p)+r(p))>>1;
if(l<=mid) query(p<<1,l,r);
if(mid<r) query(p<<1|1,l,r);
}
ll solve(int l,int r){
MS(Cnt,0);Len=0;
query(1,l,r);
if(Len<=2) return 0;
for(int i=1;i<=Len-2;i++){
if(Cnt[i+1]+Cnt[i+2]>=Cnt[i]) return 1ll*Cnt[i]+Cnt[i+1]+Cnt[i+2];
}
return 0;
}
int main(){
IOS;cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
Build(1,1,n);
for(int i=1,op,l,r;i<=q;i++){
cin>>op>>l>>r;
if(op==1) update(1,l,r);
else cout<<solve(l,r)<<"\n";
}
return 0;
}
10.26 NOIP 模拟赛 十三
100+0+100+0=200 pts,rk5。
神秘比赛放两道黑。T4 73pts 很好想,懒得写了。
有大神 Vector_net T4 假做法拿满。
T1
神秘题,打表发现只有在 \(A\dots AB\dots B\) 时输出 \(B\)。
T2
黑题不会。
T3
给出了一个 \(N\) 个点 \(M\) 条边的有向图。求有多少个有序点对 \((a,b)\),满足至少存在一个点 \(c\) 以及从 \(c\) 到 \(a\) 的一条路径 \(L_a\),\(c\) 到 \(b\) 的一条路径 \(L_b\),使得 \(L_a\) 的长度是 \(L_b\) 的两倍(长度指的经过的边的数目)\((|L_a|,|L_b|\ge 0)\)。
注意不一定是简单路径。
bfs,每次 \(a\) 走两步,\(b\) 走一步,分步进行。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=3e3+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,m;
bool vis[N][N][5];
vector<int> to[N];
struct node{
int u,v,stp;
};
int main(){
IOS;cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
to[u].pb(v);
}
int ans=0;
queue<node> q;
for(int i=1;i<=n;i++){
vis[i][i][3]=1;
q.push({i,i,3});
}
while(!q.empty()){
node tp=q.front();q.pop();
if(tp.stp==3){
for(int v:to[tp.u]){
if(vis[v][tp.v][1]) continue;
q.push({v,tp.v,1});
vis[v][tp.v][1]=1;
}
}
else if(tp.stp==1){
for(int v:to[tp.v]){
if(vis[tp.u][v][2]) continue;
q.push({tp.u,v,2});
vis[tp.u][v][2]=1;
}
}
else{
for(int v:to[tp.v]){
if(vis[tp.u][v][3]) continue;
q.push({tp.u,v,3});
vis[tp.u][v][3]=1;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) ans+=vis[i][j][3];
}
cout<<ans<<"\n";
return 0;
}
T4
黑不会,还没订正。
10.27 NOIP 模拟赛 十四
100+5+0+0=105pts,rk14。
T2 拼尽全力想出正解但写挂了。我太菜了
T1
你在一条数轴 \(1 \sim N\) 上放点,初始时 \(0\) 和 \(N+1\) 已有点。
规则:选择间隔最大的两点(包括边界点,多个最长时选最左的),在两点中间的位置放入新点(如果两点中间的点数恰好是奇数,则放在正中间,否则停在正中间的左边位置)。
每个第 \(i\) 次放入的点有一个 \(a_i\),要求放入后,离它最近的 非边界点 的距离 严格大于 \(a_i\),否则这个点会消失。
你可以花费 \(1\) 的代价使 \(a_i\) 减 \(1\)。
求最小的代价,使让所有 \(M\) 个点都成功放下。
\(N\le 10^{12},M\le 10^6\)
简单题,考虑求出 \(pos_i\) 表示 \(i\) 放的位置。用优先队列维护每个区间即可。最后统计答案。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int m;
ll n,a[N],v[N];
struct lines{
ll first,second;
};
priority_queue< lines,vector<lines>,greater<lines> > s;
bool operator > (const lines xx,const lines yy){
if(xx.first==yy.first) return xx.second>yy.second;
return xx.first<yy.first;
}
struct node{
ll pos,id;
}b[N];
int main(){
IOS;cin>>n>>m;
for(int i=1;i<=m;i++) cin>>a[i],b[i].id=i;
b[0].pos=-INF,b[m+1].pos=INF;
s.push((lines){n,1});
for(int i=1;i<=m;i++){
lines tp=s.top();s.pop();
ll len=(tp.first+1)>>1;
b[i].pos=tp.second+len-1;
if((tp.first-1)>>1){
s.push((lines){(tp.first-1)>>1,tp.second});
}
if(tp.first>>1){
s.push((lines){tp.first>>1,b[i].pos+1});
}
}
sort(b+1,b+1+m,[&](const node xx,const node yy){
return xx.pos<yy.pos;
});
for(int i=1;i<=m;i++){
v[b[i].id]=min(b[i].pos-b[i-1].pos,b[i+1].pos-b[i].pos);
}
ll ans=0;
for(int i=1;i<=m;i++){
if(a[i]-v[i]+1>0) ans+=a[i]-v[i]+1;
}
cout<<ans<<"\n";
return 0;
}
T2
地图上有 \(n\) 个点,构成一棵树形结构。初始你在 \(1\) 号点,对于 \(2 ∼ n\) 中的每个点,上面都恰有一只怪兽。
每只怪兽都可以用一个二元组 \((a_i,b_i)\) 表示。
当你第一次碰到它时,它会与你开战,先对你造成 \(a_i\) 点伤害,战斗结束后你可以恢复 \(b_i\)点血量。此后再次经过它所在节点不会产生任何影响。
你可以在地图上移动并决策打怪兽的顺序,但如果某一时刻你的血量变成负数,那么你就输了。求取得最终胜利,初始血量的最小值。
神秘贪心题。
题目转化为初始血量为 \(0\),求在所有时刻血量最小值的最大值。
考虑将两个怪 \((a_1,b_1),(a_2,b_2)\) 合并,先打 \(1\) 在打 \(2\),会有:
在考虑两只怪的优先级问题。
称一只怪 \(i\) 有益当且仅当 \(a_i \le b_i\)。
如果在菊花图上,会有一个打怪策略:
- 若一些怪有益,按 \(a_i\) 从小到大排。
- 若一些怪无益,按 \(b_i\) 从大到小排。
- 有益在无益前面。
这个顺序称为怪的优先级。
将其放到树上,如果一个点 \(u\) 的优先级最高的儿子 \(v\)。若它优先级比 \(v\) 高,因为 \(u\) 是所有能打的怪中优先级最高的的,那么打完 \(u\) 之后肯定会先打 \(v\)。那么将 \(u\) 与 \(v\) 合并,直到最后只剩一个点为止。
可以写并查集维护或是 dfs + 启发式合并。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,ff[N];
ll a[N],b[N];
vector<int> to[N];
struct node{
ll x,y;
int id;
};
bool operator > (const node xx,const node yy){
if(xx.x>=xx.y && yy.x<yy.y) return 1;
else if(xx.x<xx.y && yy.x>=yy.y) return 0;
else if(xx.x<=xx.y && yy.x<=yy.y) return xx.x>yy.x;
else return xx.y<yy.y;
}
priority_queue<node,vector<node>,greater<node> > q[N];
void dfs(int u,int fa){
ff[u]=fa;
for(int v:to[u]){
if(v==fa) continue;
dfs(v,u);
if(q[v].size()>q[u].size()) swap(q[v],q[u]);//启发式合并
}
for(int v:to[u]){
if(v==fa) continue;
while(!q[v].empty()) q[u].push(q[v].top()),q[v].pop();
}
while(!q[u].empty()){
auto tp=q[u].top();
if(node{a[u],b[u]}>tp){
if(b[u]>=tp.x) b[u]-=tp.x-tp.y;
else a[u]-=b[u]-tp.x,b[u]=tp.y;
q[u].pop();
}
else break;
}
q[u].push({a[u],b[u],u});
}
int main(){
IOS;cin>>n;
for(int i=2;i<=n;i++) cin>>a[i]>>b[i];
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
to[u].pb(v);
to[v].pb(u);
}
dfs(1,0);
while(!q[1].empty()){
node tp=q[1].top();q[1].pop();
if(b[0]>=tp.x) b[0]-=tp.x-tp.y;
else a[0]-=b[0]-tp.x,b[0]=tp.y;
}
cout<<a[0]<<"\n";
return 0;
}
T3
给定一棵 \(n\) 个节点的树,根为 \(1\),第 \(i\) 个点有一个权值 \(a_i\)。
对每个点 \(u\),这次询问答案为其所在子树外的所有点中,选两个点 \(i,j\),\(ai,aj\) 异或后的最大值,如果选不出两个点,则认为 \(u\) 的答案是 \(0\)。
原题 lg-P8511
01-Trie 维护,可以先求出整棵树的异或最大值以及位置,设为 \(x,y\),会有除了 \(1\) 到 \(x\) 的路径以及 \(1\) 到 \(y\) 路径上的点以外的点的答案都为 \(a_x \oplus a_y\)。再单独求 \(1\) 到 \(x\) 的路径以及 \(1\) 到 \(y\) 路径上的点的答案即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=5e5+5,M=N*61+5;
const ll INF=1ll<<60,mod=998244353;
int n,cnt=1,nxt[M][2],num[M],ff[N];
ll a[N];
struct Trie{
void clear(){
MS(nxt,0);MS(num,0);cnt=1;
}
void add(ll x,int id){
int p=1;
for(int i=59;~i;i--){
int ch=(x>>i)&1;
if(!nxt[p][ch]) nxt[p][ch]=++cnt;
p=nxt[p][ch];
}
num[p]=id;
}
int query(ll x){
int p=1;
for(int i=59;~i;i--){
int ch=(x>>i)&1;
if(nxt[p][ch^1]) p=nxt[p][ch^1];
else p=nxt[p][ch];
}
return num[p];
}
}tr;
vector<int> to[N];
int ax,ay,dep[N],is[N];
ll maxx,ans[N],maxn[N];
void dfs1(int u){
dep[u]=dep[ff[u]]+1;
tr.add(a[u],u);
int id=tr.query(a[u]);
if((a[id]^a[u])>maxx){
maxx=a[id]^a[u],ax=u,ay=id;
}
for(int v:to[u]) dfs1(v);
}
int Lca(int u,int v){
if(dep[u]>dep[v]) swap(u,v);
while(dep[u]<dep[v]) is[v]=2,v=ff[v];
if(u==v) return u;
while(u^v){
is[u]=3,is[v]=2,u=ff[u],v=ff[v];
}
return u;
}
void dfs3(int u){
tr.add(a[u],u);
int id=tr.query(a[u]);
maxn[u]=max(maxn[u],a[id]^a[u]);
for(int v:to[u]){
dfs3(v);
maxn[u]=max(maxn[u],maxn[v]);
}
}
void dfs2(int u,int fa,int ID){
maxn[u]=ans[u]=maxn[fa];
tr.add(a[u],u);
maxn[u]=max(maxn[u],a[tr.query(a[u])]^a[u]);
for(int v:to[u]){
if(is[v]==1 || is[v]==ID) continue;
dfs3(v);
maxn[u]=max(maxn[u],maxn[v]);
}
for(int v:to[u]){
if(is[v]==ID || is[v]==1) dfs2(v,u,ID);
}
}
int main(){
IOS;cin>>n;
for(int i=2;i<=n;i++){
cin>>ff[i];
to[ff[i]].pb(i);
}
for(int i=1;i<=n;i++) cin>>a[i];
dfs1(1);
int lc=Lca(ax,ay),nc=lc;
while(lc) is[lc]=1,lc=ff[lc];
int ttt=0;
for(int v:to[nc]) ttt+=is[v]!=0;
for(int i=1;i<=n;i++) if(!is[i]) ans[i]=maxx;
tr.clear();dfs2(1,0,2);
MS(maxn,0);
if(ttt==2) tr.clear(),dfs2(1,0,3);
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
return 0;
}
T4
比较抽象。
10.28 NOIP 模拟赛 十五
100+100+0+0=200pts,rk3
被 T2 暴杀,导致 T3 想出做法来不及写。
T1
给你一棵 \(n\) 个节点的树, 每次询问包含第 \(i\) 条边的树上最长路径长度。
糖题,但我写了 1 个小时。我是废物
求一下直径即可。也可以换根 dp,但要特判在根的情况。
Code
屎山代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,d1,d2,dis[N],ans[N],pos[N],tot,bl[N],g[N],Dis[N],Id[N];
PII ff[N],e[N];
vector<PII> to[N];
bool vis[N],is[N];
void dfs1(int u,int fa){
for(PII tp:to[u]){
int v=tp.first,id=tp.second;
if(v==fa) continue;
ff[v]={u,id};
dis[v]=dis[u]+1;
dfs1(v,u);
}
}
void dfs2(int u,int fa,int tf){
for(PII tp:to[u]){
int v=tp.first,id=tp.second;
if(v==fa) continue;
if(is[v]) dfs2(v,u,tf),bl[id]=tf;
else if(is[u]){
dis[v]=dis[u]+1;
dfs2(v,u,u);
Dis[u]=max(Dis[u],Dis[v]+1);bl[id]=u;
}
else{
dis[v]=dis[u]+1;
dfs2(v,u,tf);
Dis[u]=max(Dis[u],Dis[v]+1);bl[id]=tf;
}
}
}
void get_d(){
PII tp={0,1};
dfs1(1,0);
for(int i=1;i<=n;i++) tp=max(tp,{dis[i],i});
d1=tp.second;
MS(ff,0);MS(dis,0);dfs1(d1,0);
tp={0,d1};
for(int i=1;i<=n;i++) tp=max(tp,{dis[i],i});
d2=tp.second;
}
int main(){
IOS;cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
to[u].eb(v,i);
to[v].eb(u,i);
e[i]={u,v};
}
get_d();
int now=d2;
while(now){
PII tp=ff[now];
is[now]=1;
pos[++tot]=now;
bl[tp.second]=d1;
now=tp.first;
}
for(int i=1;i<=tot;i++) g[pos[i]]=max(i-1,tot-i);
MS(dis,0);
dfs2(d1,0,d1);
for(int i=1;i<n;i++){
if(bl[i]!=d1){
int u=e[i].first,v=e[i].second;
if(Dis[u]>Dis[v]) swap(u,v);
ans[i]=g[bl[i]]+Dis[u]+dis[u];
}
else ans[i]=tot-1;
}
for(int i=1;i<n;i++) cout<<ans[i]<<"\n";
return 0;
}
T2
给出一个 \(n\) 个点,\(m\) 条边的无向图。接下来 \(Q\) 次询问,每次从图中删掉一个点 \(A\) 后,请问此时点 \(B\) 到点 \(C\) 的最短路长度。
每次询问独立。
Floyd + 分治 + 离线。
设 \(solve(l,r)\) 表示不经过 \(l \sim r\) 这些点的最短路。当 \(l=r\) 时即为删去 \(l\) 点时的答案。
具体的在递归进 \(solve(mid+1,r)\) 时要先将 \(l \sim mid\) 的点处理了。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=200+5,M=1e5+5;
const ll INF=1e9,mod=998244353;
int n,m,Q;
ll f[N][N][N],ans[M];
struct Qry{
int u,v,id;
};
vector<Qry> q[N];
void solve(int l,int r,int d){
if(l==r){
for(Qry tp:q[l]) ans[tp.id]=f[d-1][tp.u][tp.v];
return ;
}
int mid=(l+r)>>1;
MC(f[d],f[d-1]);
for(int k=mid+1;k<=r;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[d][i][j]=min(f[d][i][j],f[d][i][k]+f[d][k][j]);
}
}
}
solve(l,mid,d+1);
MC(f[d],f[d-1]);
for(int k=l;k<=mid;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[d][i][j]=min(f[d][i][j],f[d][i][k]+f[d][k][j]);
}
}
}
solve(mid+1,r,d+1);
}
int main(){
IOS;cin>>n>>m>>Q;
for(int i=0;i<=n+1;i++){
for(int j=0;j<=n+1;j++){
for(int k=0;k<=n+1;k++) f[i][j][k]=INF;
}
}
for(int i=1,u,v;i<=m;i++){
ll w;cin>>u>>v>>w;
f[0][u][v]=f[0][v][u]=min(f[0][u][v],w);
}
for(int t=1,x,y,z;t<=Q;t++){
cin>>x>>y>>z;
q[x].pb({y,z,t});
}
solve(1,n,1);
for(int i=1;i<=Q;i++) cout<<(ans[i]==INF?-1:ans[i])<<"\n";
return 0;
}
T3
数轴上有 \(n\) 个点,第 \(i\) 个点的位置为 \(p_i\),权值为 \(a_i\)。
有以下两种操作:
- \(A~~l~~r~~c\):\(\forall i \in [l, r],\ a_i \gets a_i + c\)
- \(B~~x~~y\):将数组 \(a_{\min(x,y)\sim \max(x,y)}\) 向 \(x\) 循环移动 \(1\) 位。
即假设 \(a_{\min(x,y)\sim \max(x,y)}=[1,2,3,4,5]\),若 \(x=5,y=1\),数组将变为 \([5,1,2,3,4]\);若 \(x=1,y=5\),数组变为 \([2,3,4,5,1]\)。最开始和每次操作后求 \(P\) 满足 \(\sum\limits_{i=1}^n a_i\times|p_i - P|\) 最小,并输出 \(P\)。
FHQ_Treap 板题,但考试时没时间写了。
维护区间加,区间翻转。
\(x,y\) 的循环位移可以拆成两个区间翻转。
设权值和为 \(s\),答案为平衡树上二分找到第一个点 \(x\) 满足 $ \sum \limits_{i=1}^{x} val_i> \lfloor \frac{s+1}{2} \rfloor$ 的 \(p_x\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll __int128
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define int ll
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
namespace slow{
char Buf[1<<23],*P1=Buf,*P2=Buf;
#define getchar() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
#define T template<typename type>
#define Ts template<typename type,typename... args>
T inline void read(type &x){
x=0;bool f=false;char ch=getchar();
while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
if(f) x=-x;
}
T inline void write(type o){
if(o<0) putchar('-'),o=-o;
if(o>9) write(o/10);putchar(o%10+'0');
}
Ts inline void read(type &x,args&... y){read(x),read(y...);}
Ts inline void write(type x,args... y){write(x),putchar(' '),write(y...);putchar('\n');}
}using namespace slow;
struct Tree{
int l,r;
ll siz,val,swp,pri,add,pre;
void tag(int d){
val+=d;add+=d;pre+=siz*d;
}
#define l(p) t[p].l
#define r(p) t[p].r
#define siz(p) t[p].siz
#define val(p) t[p].val
#define swp(p) t[p].swp
#define pri(p) t[p].pri
#define add(p) t[p].add
#define pre(p) t[p].pre
}t[N];
int n,m,cnt;
int root,roota,rootb,rootc;
struct FHQ_Treap{
int newnode(int x){
t[++cnt]={0,0,1,x,0,rand(),0,x};
return cnt;
}
void up(int p){
if(!p) return ;
siz(p)=siz(l(p))+siz(r(p))+1;
pre(p)=val(p)+pre(l(p))+pre(r(p));
}
void down(int p){
if(swp(p)){
swap(l(p),r(p));
if(l(p)) swp(l(p))^=1;
if(r(p)) swp(r(p))^=1;
swp(p)=0;
}
if(add(p)){
if(l(p)) t[l(p)].tag(add(p));
if(r(p)) t[r(p)].tag(add(p));
add(p)=0;
}
}
void split(int p,int x,int &L,int &R){
if(!p) return L=R=0,void();
down(p);
if(siz(l(p))<x) L=p,split(r(p),x-siz(l(p))-1,r(p),R);
else R=p,split(l(p),x,L,l(p));
up(p);
}
int merge(int x,int y){
if(!x || !y) return x|y;
down(x),down(y);
if(pri(x)<pri(y)) return r(x)=merge(r(x),y),up(x),x;
else return l(y)=merge(x,l(y)),up(y),y;
}
void update(int l,int r,ll d){
split(root,r,roota,rootb);
split(roota,l-1,roota,rootc);
t[rootc].tag(d);
roota=merge(roota,rootc);
root=merge(roota,rootb);
}
void Reverse(int l,int r){
split(root,r,roota,rootb);
split(roota,l-1,roota,rootc);
swp(rootc)^=1;
roota=merge(roota,rootc);
root=merge(roota,rootb);
}
void out(int p){
if(!p) return ;
down(p);
out(l(p));
write(val(p)),puts(" ");
out(r(p));
}
int query(ll x){
int p=root,ans=0;
while(p){
down(p);
if(!l(p)){
ans++;
if(val(p)>=x) return ans;
x-=val(p),p=r(p);
}
else if(pre(l(p))>=x) p=l(p);
else if(pre(l(p))+val(p)>=x) return ans+siz(l(p))+1;
else ans+=siz(l(p))+1,x-=pre(l(p))+val(p),p=r(p);
}
return ans;
}
}tr;
ll pos[N];
void out(int p){
cout<<"out************\n";
tr.out(p);
cout<<"\n************\n";
}
signed main(){
read(n,m);
for(int i=1,x;i<=n;i++){
read(x);
root=tr.merge(root,tr.newnode(x));
}
for(int i=1;i<=n;i++) read(pos[i]);
write(pos[tr.query((pre(root)+1)/2)]);puts("");
for(int i=1,l,r,c;i<=m;i++){
char op=getchar();
read(l,r);
if(op=='A'){
read(c);
tr.update(l,r,c);
}
else{
if(l<r) tr.Reverse(l,r),tr.Reverse(l,r-1);
else tr.Reverse(r,l),tr.Reverse(r+1,l);
}
write(pos[tr.query((pre(root)+1)/2)]);puts("");
}
return 0;
}
T4
神秘题,不会。
10.30 NOIP 模拟赛 十六
100+100+0+0=200 pts,rk2
CSP-S 前最后一场模拟赛,膜拜 bbbzzx rk1。
T1,T2全做过原题,感觉 CSP-S 要爆炸了怎么办。
T1
简单 dp,但容易写假。
设 \(f_i\) 表示以 \(i\) 结尾子串总和,找到最右边的 \(j\) 满足 \(s_{j\sim i}\) 是包含 bessie 的。
有 \(f_i=f_{j-1}+j\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=3e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n;
ll f[N],pre[N][30];
char a[N];
int get(int id){
return pre[pre[pre[pre[pre[pre[id][5]][9]][19]][19]][5]][2];
}
int main(){
IOS;cin>>(a+1);n=strlen(a+1);
for(int i=1;i<=n;i++){
MC(pre[i+1],pre[i]);
pre[i+1][a[i]-'a'+1]=i;
}
for(int i=1;i<=n;i++){
ll j=get(i+1);
if(j) f[i]=f[j-1]+j;
}
ll ans=0;
for(int i=1;i<=n;i++) ans+=f[i];
cout<<ans<<"\n";
return 0;
}
T2
启发式合并,将 \(i\) 连的边转移到编号最小的点,延迟连边。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
using namespace std;
const int N=2e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,m;
set<int> s[N];
int main(){
IOS;cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
if(u>v) swap(u,v);
s[u].insert(v);
}
ll ans=-m;
for(int i=1;i<=n;i++){;
if(s[i].empty()) continue;
ans+=s[i].size();
int u=*s[i].begin();s[i].erase(s[i].begin());
if(s[i].size()>s[u].size()) swap(s[i],s[u]);
for(int x:s[i]) s[u].insert(x);
}
cout<<ans<<"\n";
return 0;
}
T3
想到 meet in the middle,但以为复杂度不正确,就没往后面想了。
正解:
反过来改成加边。用 meet in the middle 后只用考虑其中一部分。
维护恰好经过两条边的最短路 \(d_{i,j}\)。
分类讨论,若加边 \((u,v,w)\),起点为 \(s\):
- 若 \(k=1\):当且仅当 \(u=s\) 会贡献到答案里。
- 若 \(k=2\):可以计算:\(d_{i,v}=\min(d_{i,v},d_{i,u}+w)\),\(d_{u,i}\) 同理。在枚举答案 \(ans_i=\min(ans_i,d_{s,i})\)
- 若 \(k=3\):会发现 \((u,v,w)\) 中 \(u=s\) 的只有 \(n\) 个,可以 \(n^2\) 转移,其余可以 \(\mathcal{O(n)}\) 转移。
- 若 \(k=4\):同 \(k=3\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=300+5,M=N*N+5,P=11;
const ll INF=1e9+7,mod=998244353;
int n,m,k,dis[N][N];
ll ans[M];
bool vis[N][N];
struct edge{
int u,v;
}e[M];
struct node{
ll s,k,to[N][N],d[N][N],ans[M];
void init(int x,int K){
s=x;k=K;
MS(to,0x3f);MS(d,0x3f);MS(ans,0x3f);
}
void insert(int u,int v,int w){
to[u][v]=w;
if(k==1){
if(s==u) ans[v]=w;
return ;
}
for(int i=1;i<=n;i++){
d[i][v]=min(d[i][v],to[i][u]+w);
d[u][i]=min(d[u][i],to[v][i]+w);
}
if(k==2){
for(int i=1;i<=n;i++) ans[i]=min(ans[i],d[s][i]);
return ;
}
if(k==3){
if(s==u){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans[j]=min(ans[j],d[s][i]+to[i][j]);
}
}
}
else{
for(int i=1;i<=n;i++){
ans[i]=min({ans[i],to[s][u]+d[u][i],d[s][v]+to[v][i]});
ans[v]=min(ans[v],d[s][i]+to[i][v]);
ans[u]=min(ans[u],d[s][i]+to[i][u]);
}
}
return ;
}
if(k==4){
if(s==u){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans[j]=min(ans[j],d[s][i]+d[i][j]);
}
}
}
else{
for(int i=1;i<=n;i++){
ans[i]=min({ans[i],d[s][u]+d[u][i],d[s][v]+d[v][i]});
ans[v]=min(ans[v],d[s][i]+d[i][v]);
ans[u]=min(ans[u],d[s][i]+d[i][u]);
}
}
return ;
}
}
}s,t;
int main(){
IOS;cin>>n>>k;m=n*n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) cin>>dis[i][j];
}
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v;
s.init(1,k/2);t.init(n,k-k/2);
for(int tt=m;tt>=1;tt--){
ans[tt]=INF;
for(int i=1;i<=n;i++) ans[tt]=min(ans[tt],s.ans[i]+t.ans[i]);
int u=e[tt].u,v=e[tt].v,w=dis[u][v];
s.insert(u,v,w);
t.insert(v,u,w);
}
for(int i=1;i<=m;i++) cout<<(ans[i]>=INF?-1:ans[i])<<"\n";
return 0;
}
T4
黑题不会
11.16 NOIP 模拟赛 十七
CSP-S 果然炸了。。。
100+100+50+5=255pts rk2
被 TheShuMo 以压倒性的 349 暴杀。膜拜 Vector_net 做出 T3,暴切紫题%%%%%%%。
T1
九条可怜和 Alice 有一个序列,记作 \(a\),其中有 \(n\) 个元素,编号从 \(a_1\) 到 \(a_n\)。游戏会进行若干轮直到序列中没有元素。
在每一轮中,九条可怜会先选择一个数,如果这个数是偶数,那么九条可怜的得分就会加上这个数。之后将这个数删掉。接着 Alice 会进行同样的操作,不同的是,如果是奇数,那么 Alice 的得分会加上这个数。
问两人采取最优策略的情况下,谁最后会胜利。
唐题,每次取最大的数。
T2
你需要计算满足下列长度为 \(m\) 的序列 \(a\) 的个数:
- \(m\ne 0\)
- \(a_i<a_i+1,1≤i<m\)
- \(a_m≤n\)
- \(a_i \oplus a_{i+1} \oplus a_{i+2}\ne 0,1\le i\le m−2\)
\(\oplus\) 表示异或。对给定数 \(p\) 取模。\(n \le 10^6\)。
dp 题,挺难的。但我的做法很唐,仅供参考。
题目求的所有长度的方案数总和。
考虑若 \(n \le 2000\):
可以设 \(f_{i,j}\) 表示最大的数为 \(i\),最后两个异或和为 \(j\) 的方案数,即上一个数为 \(k=i \oplus j\)。有:
记一下和。复杂度 \(\mathcal{O(n^2)}\)。
可以把状态改为 \(g_i=\sum f_{i,j}\)。有:
会发现若 \(i\oplus j<i\),则有 \(j\) 的最高位在 \(i\) 中为 \(1\) 且不为 \(i\) 的最高位。
因此记一下前缀和再删去一部分即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60;
namespace H_H{
int n,mod,num[N],K[N];
ll f[N],s[N];
int main(){
cin>>n>>mod;
if(n==1) cout<<1%mod<<"\n",exit(0);
ll sum=3;
f[1]=1;s[1]=1;
f[2]=2;s[2]=2;
for(int i=1;i<=n;i++) num[i]=num[i>>1]+1;
for(int t=3;t<=n;t++){
f[t]=(sum+1)%mod;
for(int i=t;lb(i)!=i;i-=lb(i)) f[t]=(f[t]-s[num[lb(i)]]+mod)%mod;
sum=(sum+f[t])%mod;
s[num[t]]=(s[num[t]]+f[t])%mod;
}
cout<<sum%mod<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
九条可怜有一个棋盘,是 \(n×(m+1)\) 的,上面有 \(n\) 个棋子,其中每行恰好有一个棋子,记第 \(i\) 行的棋子的纵坐标为 $ai,列的编号从 \(0\) 到 \(m\)。
如果一个棋子左边没有棋子,且左边不是棋盘边界,那么就称这个棋子是可以连续穿梭的。
Bob会和Alice玩游戏,九条可怜会每次先选择两个数 \(l,r\) 表示只保留棋盘的 \([l,r]\) 列内的格子和棋子。
Alice先手,每个人每次选择一枚棋子,如果这枚棋子是可以连续穿梭的,那么她会使这个棋子向左移动任意格。第一个不能操作的人输。
九条可怜在旁边电灯泡吃瓜津津有味,因此九条可怜给出了 \(Q\) 局的 \(l,r\),想让你求出谁会胜利。
对于 \(100\%\) 的数据,\(n,q,m≤3×10^5\)
会发现这是一个 Nim 博弈论。
相当于若 \(\bigoplus\limits_{i=l}^r(a_i-l)=0\) 则先手必输,否则必赢。
暴力可以拿 \(50\) 分。
可以拿 ST 表优化,按位来考虑,拿桶记一下每一种数的个数。设 \(f_{i,j}\) 表示只考虑 \([i,i+2^j-1]\) 内的数在均减去 \(i\) 的情况下的异或和。
需要考虑如何将 \(f_{i,j-1}\) 和 \(f_{i+2^{j-1},j-1}\) 合并。由于对于两个区间的数在减完后一定小于 \(2^{j-1}\),所以只用考虑第 \(j-1\) 的情况。
会发现在右部分会多减 \(2^{j-1}\),因此要把它加回来。由于异或的特性,只有在多减的数为奇数个时才会为 \(1\)。判断一下即可。
查询也是按位考虑。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=3e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n,m,q,num[N],f[N][23];
int main(){
IOS;cin>>n>>m;
for(int i=1,x;i<=n;i++) cin>>x,num[x]++;
for(int i=1;i<=m;i++) num[i]+=num[i-1];
for(int j=1;j<=__lg(m);j++){
for(int i=0;i+(1<<j)-1<=m;i++){
f[i][j]=f[i][j-1]^f[i+(1<<(j-1))][j-1];
if((num[i+(1<<j)-1]-num[i+(1<<(j-1))-1])&1) f[i][j]^=1<<(j-1);
}
}
cin>>q;
for(int i=1,l,r;i<=q;i++){
cin>>l>>r;
int tp=0,now=l;
for(int t=20;~t;t--){
if(now+(1<<t)<=r){
tp^=f[now][t];now+=1<<t;
if((num[r]-num[now-1])&1) tp^=1<<t;
}
}
if(!tp) cout<<"B";
else cout<<"A";
}
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T4
题目看不懂,不会。
11.17 NOIP 模拟赛 十八
100+100+68+36=304 pts,rk3 败在两位高二大神上。
T1
唐题
T2
给定 \(N\) 个 \(M\) 位的 \(01\) 串。对于每个串,求与它不一样位的个数最多的串,输出位数。\(N\le 10^5,M\le 18\)。
发现与 NOIP 模拟赛十一的 T2 几乎一样,将代价改为 \(1\) 即可。
复杂度 \(\mathcal{O(2^m m^2)}\)。赛后发现可以 \(\mathcal{O(2^m m)}\),懒得管了。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e5+20,M=(1<<18)+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int m,n,a[N],num[M],f[20][M][20];
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
char ch;cin>>ch;
if(ch=='G') a[i]|=1<<j;
}
num[a[i]]=1;
}
for(int i=0;i<(1<<m);i++) f[0][i][0]=num[i];
for(int i=1;i<=m;i++){
for(int t=0;t<(1<<m);t++){
for(int j=0;j<=m;j++){
if(j) f[i][t][j]=f[i-1][t][j-1];
f[i][t][j]|=f[i-1][t^(1<<(i-1))][j];
}
}
}
for(int i=1;i<=n;i++){
int c=(~a[i])&((1<<m)-1);
for(int j=m;~j;j--){
if(f[m][c][j]){
cout<<j<<"\n";
break;
}
}
}
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
死在了复杂度分析上。我还是太菜了。
先考虑暴力的做法。
设 \(\texttt{G}\) 为 \(1\),\(\texttt{H}\) 为 \(0\)。
假设枚举到的区间为 \([L,R]\),有 \(k\) 个 \(1\),位置为 \(a_{1\sim k}\),满足其为回文串的充要条件是 \(1\) 是对称的。
当 \([L,R]\) 长度为偶数且 \(1\) 的个数为奇数时显然是无解的。
要满足交换的次数最少,则一定是 \(a_1\) 与 \(a_k\) 交换后关于中心对称,\(a_2\) 与 \(a_{k-1}\) 后关于中心对称等。贡献为 \(| a_i-L-(R-a_{k-i+1})|=| a_i+a_{k-i+1}-L-R|\),即 \(i\) 和 \(k-i+1\) 两个分别在中间的两侧。
每次求可以固定 \(L\),让 \(R\) 往右枚举,把为 \(1\) 的位置记录下来。复杂度 \(\mathcal{O(n^3)}\)。
正解:
若 \(k\) 为奇数,可以固定第 \(\lfloor\frac{k+1}{2}\rfloor\) 的数,偶数同理。每次向两边各扩展一个 \(1\),设分别为 \(a_l,a_r\)。那么 \(L \in (a_{l-1},a_l]\),\(R \in [a_r,a_{r+1})\)。
可以直接枚举 \(L,R\),对于每个 \(L,R\) 只会被枚举一次。因为 \(a_l,a_r\) 中点只有一个(显然的),它只会被枚举到一次,所以 \(L \in (a_{l-1},a_l]\),\(R \in [a_r,a_{r+1})\) 也只会被枚举到一次。
拿树状数组维护有多少个数比 \(L+R\) 大以及它们的和,有多少个数比 \(L+R\) 小以及它们的和。分别计算贡献即可。复杂度 \(\mathcal{O(n^2\log n)}\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=7500+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n,a[N],b[N],cnt;
struct BIT{
ll s[N<<1],p[N<<1];
inline void clear(){MS(s,0);MS(p,0);}
inline void add(int x){
int tp=x;
for(;x<=2*n;x+=lb(x)) s[x]+=tp,p[x]+=1;
}
inline PLL query(int x){
ll ans=0,Ans=0;
for(;x;x-=lb(x)) ans+=s[x],Ans+=p[x];
return {ans,Ans};
}
}bit;
PLL operator - (const PLL xx,const PLL yy){
return {xx.first-yy.first,xx.second-yy.second};
}
inline void input(){
char ch;
while(cin>>ch) a[++n]=ch=='G';
}
int main(){
input();
ll ans=0;
for(int l=n;l;l--){
cnt=0;
for(int r=l;r<=n;r++){
if(a[r]) b[++cnt]=r;
if((r-l)&1 && cnt&1){//无解
ans--;
continue;
}
if(cnt&1) ans+=abs(((l+r)>>1)-b[(cnt>>1)+1]);
}
}
b[cnt+1]=n+1;
for(int i=1;i<cnt;i++){//k 为偶数
bit.clear();
for(int l=i,r=i+1;l>=1 && r<=cnt;l--,r++){
bit.add(b[l]+b[r]);
for(int L=b[l-1]+1;L<=b[l];L++){
for(int R=b[r];R<=b[r+1]-1;R++){
int num=L+R;
PLL num_1=bit.query(num);
PLL num_2=bit.query(2*n)-num_1;
ans+=(num_1.second-num_2.second)*num+num_2.first-num_1.first;
}
}
}
}
for(int i=2;i<cnt;i++){//k 为奇数
bit.clear();
for(int l=i-1,r=i+1;l>=1 && r<=cnt;l--,r++){
bit.add(b[l]+b[r]);
for(int L=b[l-1]+1;L<=b[l];L++){
for(int R=b[r];R<=b[r+1]-1;R++){
if((L-R)&1) continue;
int num=L+R;
PLL num_1=bit.query(num);
PLL num_2=bit.query(2*n)-num_1;
ans+=(num_1.second-num_2.second)*num+num_2.first-num_1.first;
}
}
}
}
cout<<ans<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T4
动态 DP,不会。
11.18 NOIP 模拟赛 十九
100+10+0+0=110 pts,rk1145141919810
T1
有一天有 \(m\) 只虱子旅行到你的头上。虱子会在 \(10^6\) 天后离去,但是这 \(10^6\) 天显然非常难熬。如果某一天第 i 只虱子在你头上活动,你的难受程度会加上 \(c_i\)。
为了定量计算你的难受程度,你和虱子首领要到了 \(n\) 条信息,每个信息可以表示为 \(L,R,K\),以及 \(K\) 个整数 \(A_i\),表示在 \(L_i\) 到 \(R_i\) 天范围内 \(A\) 所表示的 \(K\) 只虱子会在你头上活动。注意,如果多条信息都提到了某个虱子在某一天活动过,你这一天的难受程度还是只会计算一次。
但是不幸的是,信息在到你手上之前先经过了跳蚤手上,跳蚤为了让你不能定量计算,于是加入了一条伪造的信息。
你无法分辨哪一条是伪造的信息,因此你决定对于每条信息,如果除了这条信息以外的信息都是真的话,你的难受程度总和是多少。
对于 \(100\%\) 的数据,\(1\le n,m\le 5×10^5,1\le L_i\le R_i \le 10^6,1\le \sum K\le 10^6,1≤K≤m,1≤c_i≤10^6\)。
说实话并非简单。
将每只跳蚤的贡献分开考虑。对于每只跳蚤它对第 \(i\) 条信息的贡献为将它在从第 \(i\) 条信息去除后减少的难受程度。直接做前缀和以及统计前缀区间为 \(1\) 的个数。复杂度 \(\mathcal{O(K\times 10^6)}\)。
用类似于扫描线的方法。将所有 \(L,R\) 离散,将一个点当作一个区间求。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
struct node{
int id,l,r;
node(){id=l=r=0;}
node(int _id,int _l,int _r){
id=_id,l=_l,r=_r;
}
bool operator < (const node xx){
if(l==xx.l) return r<xx.r;
return l<xx.l;
}
};
vector<node> a[N];
int n,m,c[N],b[N],s[N];
ll f[N],ans[N];
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>c[i];
for(int i=1,L,R,K;i<=n;i++){
cin>>L>>R>>K;
for(int j=1,x;j<=K;j++){
cin>>x;
a[x].pb(node(i,L,R));
}
}
ll sum=0;
for(int i=1;i<=m;i++){
if(!a[i].size()) continue;
int cnt=0;
for(node tp:a[i]) b[++cnt]=tp.l,b[++cnt]=tp.r+1;
b[++cnt]=1;
sort(b+1,b+1+cnt);//离散
cnt=unique(b+1,b+1+cnt)-b-1;
for(int j=1;j<=cnt;j++) s[j]=0;
for(node tp:a[i]){//差分
int ld=lower_bound(b+1,b+1+cnt,tp.l)-b;
int rd=lower_bound(b+1,b+1+cnt,tp.r+1)-b;
s[ld]++,s[rd]--;
}
for(int j=1;j<=cnt;j++) s[j]+=s[j-1];
for(int j=2;j<=cnt;j++){
f[j]=f[j-1]+(s[j-1]==1?(b[j]-b[j-1]):0);
sum+=1ll*c[i]*(s[j-1]?(b[j]-b[j-1]):0);
}
for(node tp:a[i]){//统计
int ld=lower_bound(b+1,b+1+cnt,tp.l)-b;
int rd=lower_bound(b+1,b+1+cnt,tp.r+1)-b-1;
ans[tp.id]-=1ll*(f[rd+1]-f[ld])*c[i];
}
}
for(int i=1;i<=n;i++) cout<<sum+ans[i]<<" ";cout<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T2
有 n×n 个点排成了一个 n 行 n 列的点阵,对于每个 1≤i,j≤n,(i,j) 上都有一个点。
但是全部 n×n 个点去同一个地方总不太好,因此一部分点想要去 T,一部分点想要去 P。怎么办呢?
珂爱的点们决定,用一条直线将平面分成两半,直线左边的去T,有边的去P。
特别的,如果直线左边或者右边没有点,那么这种划分方案是不合法的。如果这条直线经过一个点,那么这个点就不知道自己去哪儿,因此也是不合法的。
求本质不同的划分方案数。两个方案本质不同定义为:一个点在其中一个方案中去了 P 而另一个去了 T。\(n\le 10^4\)
sb 题,做不来.
等价于矩阵可见点对个数。
可以考虑一条至少经过两个点的直线,可以将其按两点中间顺时针旋转一点角度即可得到一种方案。
会发现可以按顺时针和逆时针转,但会有算重的情况,故只统一往一个方向转。
要那数组记录一下 \(\gcd\),复杂度 \(\mathcal{O(n^2)}\),可以拿莫反优化到 \(\mathcal{O(n)}\),用杜教筛优化到到 \(\mathcal{O(n^{\frac{2}{3}})}\)。
Code
给出 \(\mathcal{O(n^2)}\) 做法。注意可能算重的地方。
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
//#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e4+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int gd[N][N];
inline int gcd(int a,int b){
if(gd[a][b]) return gd[a][b];
if(!b) return a;
return gd[a][b]=gcd(b,a%b);
}
int n,p;
int main(){
cin>>n>>p;
ll ans=0;
for(int i=0;i<=n;i++){
for(int j=0;j<i;j++){
if(gcd(i,j)==1) ans+=1ll*(n-i)*(n-j);
}
}
ans*=4;
ans-=(n-1)*2;
cout<<ans%p<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
不会。
T4
不会。
11.20 NOIP 模拟赛 二十
100+100+80+0=280pts,rk4。
凭什么 T3 都是 \(\mathcal{O(\frac{n^3\log n}{w})}\) 他们的跑这么快,甚至暴力能过。
T1
M 得到了一个正整数序列 \(a\),值域为 \([1,m]\)。方便起见 \(a\) 中的数互不相同。
M 非常喜欢完全平方数,因此 M 喜欢找到一对 \(i,j\),计算 \(a^2_i+a_j\),同时要求这也是一个完全平方数。
但是 M 并不会这样复杂的问题,请你告诉它这样的 \((i,j)\) 的对数。
设 \(a_i^2+a_j=x^2\),移项得 \(a_j=(x-a_i)(x+a_i)\),考虑枚举 \(a_j\) 由哪两个数相乘得到。直接做即可。
预处理因数,复杂度 \(\mathcal{O(m\log m)}\)。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
#define int ll
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H_READ{
char Buf[1<<23],*P1=Buf,*P2=Buf;
#define gc() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
Tp inline void read(T &x){
x=0;int ch=gc();bool f=0;
while(!isdigit(ch)) f=ch=='-',ch=gc();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=gc();
if(f) x=-x;
}
Tp inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) putchar(x/10);putchar(x%10+'0');
}
Ts inline void read(T &x,args&... y){read(x),read(y...);}
Ts inline void write(T x,args... y){write(x),putchar(' '),write(y...);puts("");}
}using H_H_READ::read;using H_H_READ::write;
namespace H_H{
int n,m;bool a[N];
int main(){
read(n,m);
for(int i=1,x;i<=n;i++) read(x),a[x]=1;
ll ans=0;
for(int t=1;t<=m;t++){
for(int i=1;i<=m/t;i++){
int u=t,v=i;
if(!a[i*u] || (u+v)&1) continue;
if(u>v) swap(u,v);
ans+=a[(v-u)>>1];
}
}
cout<<ans/2<<"\n";
return 0;
}
}
signed main(){
H_H::main();
return 0;
}
T2
给出一个 \(1\) 到 \(N\) 的排列,你需要求出有多少个区间 \([L,R]\),满足这个区间的值是连续的。比如 \(2,3,1\) 是一个合法的区间,而 \(3,1\) 不是。
对于一个区间 \([l,r]\) 若符合条件就等价于 \(mx-mn+l-r=0\)。固定 \(r\),用线段树维护区间最小值及个数,一段范围的最大和最小值会发生相同的改变,拿单调栈维护 \(mx,mn\) 即可。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=3e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n,a[N];
struct Tree{
int l,r,add;
PII pre;
inline void tag(int d){
pre.first+=d;add+=d;
}
#define l(p) t[p].l
#define r(p) t[p].r
#define pre(p) t[p].pre
#define add(p) t[p].add
}t[N<<2];
inline PII get(PII xx,PII yy){
return xx.first==yy.first?make_pair(xx.first,xx.second+yy.second):min(xx,yy);
}
inline void up(int p){
pre(p)=get(pre(p<<1),pre(p<<1|1));
}
inline void down(int p){
if(add(p)) t[p<<1].tag(add(p)),t[p<<1|1].tag(add(p)),add(p)=0;
}
inline void Build(int p,int l,int r){
l(p)=l,r(p)=r;
if(l==r) return pre(p)={l,1},void();
int mid=(l+r)>>1;
Build(p<<1,l,mid);Build(p<<1|1,mid+1,r);
up(p);
}
inline void update(int p,int l,int r,int d){
if(l<=l(p) && r(p)<=r) return t[p].tag(d);
down(p);
int mid=(l(p)+r(p))>>1;
if(l<=mid) update(p<<1,l,r,d);
if(mid<r) update(p<<1|1,l,r,d);
up(p);
}
inline PII query(int p,int l,int r){
if(l<=l(p) && r(p)<=r) return pre(p);
down(p);
int mid=(l(p)+r(p))>>1;PII ans={1e9,0};
if(l<=mid) ans=get(ans,query(p<<1,l,r));
if(mid<r) ans=get(ans,query(p<<1|1,l,r));
return ans;
}
int stk1[N],top1,stk2[N],top2;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
Build(1,1,n);
ll ans=0;
for(int i=1;i<=n;i++){
update(1,1,n,-1);
while(top1 && a[stk1[top1]]<=a[i]){
update(1,stk1[top1-1]+1,stk1[top1],a[i]-a[stk1[top1]]);
top1--;
}
while(top2 && a[stk2[top2]]>=a[i]){
update(1,stk2[top2-1]+1,stk2[top2],a[stk2[top2]]-a[i]);
top2--;
}
stk1[++top1]=i;stk2[++top2]=i;
PII tp=query(1,1,i);
if(!tp.first) ans+=tp.second;
}
cout<<ans<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
初始有一个数组 \({1,2,…,n}\),其中 \(n\) 为偶数。
每次操作你可以选择相邻两个数 \(i,j\),把他们同时从数组中删去,费用是 \(c(i,j)\),其中 \(c\) 是给定的矩阵。
注意,两个数删除后,其位置不保留,也就是说他们两侧的数会变成相邻的。
在 \(n^2\) 次操作后,数组会被删空。定义总费用为所有操作的费用的最大值,你需要求出最小总费用。\(n\le 4000\)
二分答案 + 区间 dp + bitset优化
具体看代码的写法。
Code
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=4000+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H_READ{
char Buf[1<<23],*P1=Buf,*P2=Buf;
#define gc() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
Tp inline void read(T &x){
x=0;int ch=gc();bool f=0;
while(!isdigit(ch)) f=ch=='-',ch=gc();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=gc();
if(f) x=-x;
}
Tp inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) putchar(x/10);putchar(x%10+'0');
}
Ts inline void read(T &x,args&... y){read(x),read(y...);}
Ts inline void write(T x,args... y){write(x),putchar(' '),write(y...);puts("");}
}using H_H_READ::read;using H_H_READ::write;
namespace H_H{
int n,c[N][N];
bool w[N][N];
bitset<N> f[N];
bool check(int mid){
MS(f,0);
for(int i=1;i<=n;i++) f[i][i-1]=1;
for(int l=n-1;l;l--){
for(int k=l+1;k<=n;k+=2){
if(f[l+1][k-1]&&!f[l][k]&&c[l][k]<=mid){
f[l][k]=1;f[l]|=f[k+1];
// for(int r=k+2;r<=n;r+=2) if(f[k+1][r]) f[l][r]=1;//暴力写法
}
}
}
return f[1][n];
}
int main(){
read(n);
MS(c,0x3f);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j+=2){
read(c[i][j]);
c[j][i]=c[i][j];
}
}
int l=1,r=(n>>1)*(n>>1);
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
return 0;
}
}
int main(){
H_H::main();
return 0;
}
T4
有 \(n\) 个矩形,求有多少个三元组满足对应的矩形不交 \(n\le 2\times 10^5\)
暴力没写完我是sb。
可以拿 bitset 优化到 \(\frac{n^3}{w}\)。
11.21 NOIP 模拟赛 二一
0+100+80+50=230 pts。
已严肃完成今日 T1 写假,T3 没特判大学习。
T1
求是否存在长度为 \(n\) 的数组满足 \(x_1=A,x_n=B,\forall 1<i\le n,C≤|x_i−x_{i−1}|≤D\)。
考虑前一部分为正增长,之后为负增长。枚举后判断一下即可。
Code
其实是贺别人的
点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n;
ll A,B,C,D;
int main(){
cin>>n>>A>>B>>C>>D;
for(int i=1;i<n;i++){
ll L1=A+C*i,R1=A+D*i;
ll L2=B+C*(n-i-1),R2=B+D*(n-i-1);
if(L1>=L2&& L1<=R2 || L2>=L1&&L2<=R1) return puts("YES"),0;
}
puts("NO");
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}

浙公网安备 33010602011771号