【学习笔记】NOIP信心赛
签到题
不太理解出题人为啥要放
n
=
300
n=300
n=300的丑陋测试点
如果我们把坐标系旋转 45 45 45度,那么上下左右就变成了左上,右上,左下,右下。
那么把它交错排起来就能得到 3 n + 4 3n+4 3n+4个点的做法。
但是毒瘤的出题人就是要卡
n
=
300
n=300
n=300啊,怎么办呢
没办法,只能搞一个比 3 n 3n 3n还小的方案了。
只需把宽度增大(这样耗费的点数更少),最后再连一个环即可。只用了 512 512 512个点。理论上可以更少。
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
using namespace std;
int n;
vector<pair<int,int>>v;
int main(){
freopen("hello.in","r",stdin);
freopen("hello.out","w",stdout);
cin>>n;
if(n%3==0){
int x=1e5,y=1e5;
for(int i=1;i<=n/3+1;i++){
v.pb({x,y}),v.pb({x+1,y-1}),v.pb({x-1,y}),v.pb({x,y-1}),v.pb({x+1,y-2});
x--,y--;
}
v.pb({x-1,y+1}),v.pb({x+2,y-2});
v.pb({x-1,y}),v.pb({x+1,y-2});
v.pb({x-1,y-1}),v.pb({x,y-2});
v.pb({x-1,y-2});
cout<<v.size()<<"\n";
for(auto x:v){
cout<<x.fi<<' '<<x.se<<"\n";
}
return 0;
}
v.pb({1,1}),v.pb({1,2}),v.pb({2,1}),v.pb({2,2}),v.pb({2,3}),v.pb({3,2}),v.pb({3,3});
int x=3,y=3;
for(int i=1;i<n;i++){
v.pb({x,y+1}),v.pb({x+1,y}),v.pb({x+1,y+1});
x++,y++;
}cout<<v.size()<<"\n";
for(auto x:v){
cout<<x.fi<<' '<<x.se<<"\n";
}
}
小心套子
原题是 CF321D Ciel and Flipboard 。
我向来是不会思维题的。码完线性基和暴搜就跑了。
我们应该观察到如下性质,记 s i , j s_{i,j} si,j表示这个位置是否变号,那么对于同一行, s i , j ⊕ s i , m ⊕ s i , j + m = 0 s_{i,j}\oplus s_{i,m}\oplus s_{i,j+m}=0 si,j⊕si,m⊕si,j+m=0 。原因在于, ( i , m ) (i,m) (i,m)一定会被覆盖,而 ( i , j ) (i,j) (i,j)和 ( i , j + m ) (i,j+m) (i,j+m)恰好会覆盖一个。
因此只要知道左上角的状态就能还原出整个矩阵。感性认识到矩阵操作是线性无关的,那么 2 m × m 2^{m\times m} 2m×m种操作序列恰好对应左上角 2 m × m 2^{m\times m} 2m×m个格子的状态,因此贪心即可。
复杂度 O ( n 2 2 m ) O(n^22^{m}) O(n22m)。
remark
\text{remark}
remark 巧妙的观察。或许这就是oi的魅力吧
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,m,b[50][50];
ll a[50][50],res=-inf,val[50][2];
ll V(int x,int y){
return b[x][y]?-a[x][y]:a[x][y];
}
int main(){
freopen("taozi.in","r",stdin);
freopen("taozi.out","w",stdout);
cin>>n,m=(n+1)/2;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
for(int s=0;s<1<<m;s++){
ll tot=0;
for(int i=1;i<=m;i++){
b[m][i]=s>>i-1&1;
tot+=V(m,i);
}for(int i=m+1;i<=n;i++){
b[m][i]=b[m][i-m]^b[m][m];
tot+=V(m,i);
}
for(int i=1;i<m;i++){
for(int k=0;k<2;k++){
b[i][m]=k,b[i+m][m]=k^b[m][m];
val[i][k]=V(i,m)+V(i+m,m);
for(int j=1;j<m;j++){
b[i][j]=0,b[i][j+m]=b[i][j]^b[i][m],b[i+m][j]=b[i][j]^b[m][j],b[i+m][j+m]=b[i][j+m]^b[m][j+m];
val[i][k]+=abs(V(i,j)+V(i,j+m)+V(i+m,j)+V(i+m,j+m));
}
}
tot+=max(val[i][0],val[i][1]);
}
res=max(res,tot);
}cout<<res;
}
d
原题是 CF765F Souvenirs 。
数据结构好题。
长话短说,假设当前考虑到 j j j,我们每次找到满足 a j ≤ a i a_j\le a_i aj≤ai的最大的编号 j j j更新答案( a j > a i a_j>a_i aj>ai的情况将值域反转再做一次即可),考虑优化这个过程,假设上一个找到的是 j ′ j' j′,那么下一个 j j j如果对答案有贡献的话,显然 a j ′ < a j ≤ a i a_{j'}<a_j\le a_i aj′<aj≤ai,并且若 a j − a j ′ ≤ a i − a j a_j-a_{j'}\le a_{i}-a_j aj−aj′≤ai−aj那么这个 a i − a j a_i-a_j ai−aj也对答案没有贡献。观察到这样只会跳 log \log log次,因此暴力维护即可。复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
const int N=3e5+5;
int n,m,a[N],b[N],bit[N],tot,ans[N],rt[N];
vector<pair<int,int>> g[N];
void add(int x,int k) {
for(int i=x;i<=n;i+=i&-i) bit[i]=min(bit[i],k);
}
struct node{
int l,r,mx;
}t[N<<5];
int upd(int o,int l,int r,int x,int y) {
int oo=++tot;
t[oo]=t[o];
t[oo].mx=max(t[oo].mx,y);
if(l==r) {
return oo;
}
int mid=(l+r)/2;
if(x<=mid) t[oo].l=upd(t[oo].l,l,mid,x,y);
else t[oo].r=upd(t[oo].r,mid+1,r,x,y);
return oo;
}
int qry(int o,int l,int r,int ql,int qr) {
if(!o||ql>qr) return 0;
if(ql<=l&&r<=qr) {
return t[o].mx;
}
int mid=(l+r)/2;
if(qr<=mid) return qry(t[o].l,l,mid,ql,qr);
else if(mid<ql) return qry(t[o].r,mid+1,r,ql,qr);
else {
return max(qry(t[o].l,l,mid,ql,qr),qry(t[o].r,mid+1,r,ql,qr));
}
}
int ask(int x) {
int tot=inf;
for(int i=x;i;i-=i&-i) {
tot=min(tot,bit[i]);
}
return tot;
}
int rev(int l) {
return n-l+1;
}
void work() {
for(int i=1;i<=n;i++) {
int l=qry(rt[i-1],0,1e9,a[i],1e9);
while(l) {
add(rev(l),a[l]-a[i]);
l=qry(rt[l-1],0,1e9,a[i],(a[i]+a[l]-1)/2);
}
for(auto x:g[i]) {
ans[x.se]=min(ans[x.se],ask(rev(x.fi)));
}
rt[i]=upd(rt[i-1],0,1e9,a[i],i);
}
while(tot>0) {
t[tot]=t[0];
tot--;
}
memset(bit,0x3f,sizeof bit);
}
int main() {
scanf("%d",&n);memset(ans,0x3f,sizeof ans);memset(bit,0x3f,sizeof bit);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++) {
int l,r;scanf("%d%d",&l,&r);
g[r].push_back(make_pair(l,i));
}
work();for(int i=1;i<=n;i++) a[i]=1e9-a[i];work();
for(int i=1;i<=m;i++) {
printf("%d\n",ans[i]);
}
}
再来一道类似的题目:[WC2022] 秃子酋长
感觉做法和上一道完全不同啊。
首先问题转化为从 [ l , r ] [l,r] [l,r]的最小值开始,在原序列上走到下一个值的位置,走的总步数。
考虑分治 为啥啥都能分治 。也就是说每次处理跨过
mid
\text{mid}
mid的询问。
左右分别排序,然后以考虑右边点贡献为例。假设排序后 i i i, j j j相邻( a i < a j a_i<a_j ai<aj),那么有两种情况:
1.1
1.1
1.1 左边存在
a
i
a_i
ai,
a
j
a_j
aj之间的数,那么一定是
i
i
i先走到左边,在左边走走走后回到右边,贡献
i
+
j
i+j
i+j
1.2
1.2
1.2 左边不存在
a
i
a_i
ai,
a
j
a_j
aj之间的数,那么直接从
i
i
i走到
j
j
j,贡献
∣
j
−
i
∣
|j-i|
∣j−i∣
对于第一种贡献产生的条件,显然找到左区间最靠右的中间的数,那么询问左端点应该在它的左边。可以用树状数组维护。
最后做一遍扫描线,可以用线段树之类的维护一下前驱后继。
复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n) 。理论上可以吊打根号。
代码咕掉了。