A
题目大意:
无限大棋盘上给定两个棋子的坐标,问有多少个位置能够使得能走 ( a , b ) (a,b) (a,b) 格的马捉双
思路:
用map存,模拟即可,学到了make_pair(a,b)和{a,b}等价
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
map<pii,int> mp;
int dx[]={1,-1,1,-1};
int dy[]={-1,1,1,-1};
void work()
{
int a,b,qx,qy,kx,ky;
cin>>a>>b;
cin>>kx>>ky>>qx>>qy;
for(int i=0;i<4;i++)
{
mp[make_pair(kx+a*dx[i],ky+b*dy[i])]++;
if(a!=b) mp[make_pair(kx+b*dx[i],ky+a*dy[i])]++;
mp[make_pair(qx+a*dx[i],qy+b*dy[i])]++;
if(a!=b) mp[make_pair(qx+b*dx[i],qy+a*dy[i])]++;
}
int ans=0;
trav(item,mp)
if(item.second>1) ans++;
cout<<ans<<'\n';
mp.clear();
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
B
题目大意:
给定一个数组 a a a,有以下规则
如果当前分数大于或等于 a i a_i ai,那么你可以将当前分数增加 a i a_i ai,并将 a i a_i ai从数组中移除
一开始可以任意选择一个 a i a_i ai移除并作为初始点数
对于每个 i i i,输出以 a i a_i ai开始可以移除的最多数的个数
1 ≤ n ≤ 1 e 5 1 \le n \le 1e5 1≤n≤1e5
思路:
排序,如果大数被移除,那么小数一定可以被移除
不难想到设前缀和为 s i s_i si,显然当 s i − 1 > = a i s_{i-1}>=a_i si−1>=ai时这些数都可以移除
那么对于每个 i i i而言,只需要找到最小的 i < = j i<=j i<=j且满足 s j − 1 < a [ j ] s_{j-1}<a[j] sj−1<a[j]的 j j j就是答案了
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const int maxn=1e5+5;
map<int,int> f;
int n;
int a[maxn],b[maxn];
int ans[maxn];
ll s[maxn];
void clean()
{
f.clear();
}
void work()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++) f[a[i]]=i,s[i]=s[i-1]+a[i];
ans[n]=n;
for(int i=n-1;i>=1;i--)
if(s[i]>=a[i+1]) ans[i]=ans[i+1];
else ans[i]=i;
for(int i=1;i<=n;i++)
cout<<ans[f[b[i]]]-1<<' ';
cout<<'\n';
clean();
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
C
题目大意:
给定一个数组 a a a,每次从中选两个数 a i , a j , i ≠ j a_i,a_j,i \ne j ai,aj,i=j,将 ∣ a i − a j ∣ |a_i-a_j| ∣ai−aj∣加入到 a a a数组中
求恰好 k k k次这样的操作后, a a a数组的最小值
1 ≤ n ≤ 5000 1 \le n \le 5000 1≤n≤5000
思路:
构造题熟练的同学应该很容易观察到 k ≥ 3 k \ge 3 k≥3时显然答案为 0 0 0, k = 1 和 k = 2 k=1和k=2 k=1和k=2的情况暴力做就可以了
需要注意的点
官方文档中的lower_bound和upperbound,需要保证容器有序
upper_bound()函数
1、返回指向范围[first,last)中第一个元素使得value < element(或comp(value,element))为true(即严格大的迭代器),如果找不到这样的元素,则为last。
2、[first,last)必须根据表达式!(value < element)或!comp(value,element)进行分区,即表达式为true的所有元素必须在表达式为false的所有元素之前!完全排序的[first,last)符合此条件
3、如果没有自定义比较函数就使用operator<来比较element,如果自定义了比较函数就使用comp来比较element
lower_bound()函数
1、返回指向范围[first,last)中的第一个元素使得该元素不满足element< value(或comp(element,value)为false)、(即大于或等于)的迭代器,如果找不到这样的元素,则返回last
2、[first,last)必须相对于表达式element< value(或comp(element,value))进行分区,即表达式为true的所有元素必须在表达式为false的所有元素之前。完全排序的[first,last)符合此条件
3、如果没有自定义比较函数就使用operator<来比较element,如果自定义了比较函数就使用comp来比较element
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const ll inf=1e18+5;
const int maxn=2e5+5;
int n,k;
ll a[maxn];
void work()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
if(k>=3)
{
cout<<"0\n";
return;
}
sort(a+1,a+n+1);
if(k==1)
{
ll ans=inf;
for(int i=1;i<=n;i++) ans=min(ans,a[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
ans=min(ans,abs(a[i]-a[j]));
cout<<ans<<'\n';return;
}
else
{
ll ans=inf;
for(int i=1;i<=n;i++) ans=min(ans,a[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
ans=min(ans,abs(a[i]-a[j]));
int xl=lower_bound(a+1,a+n+1,abs(a[i]-a[j]))-a-1;
ans=min(ans,abs(a[xl]-abs(a[i]-a[j])));
if(xl!=n) ans=min(ans,abs(a[xl+1]-abs(a[i]-a[j])));
}
cout<<ans<<'\n';
}
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
D
题目大意:
给定两个序列 a , b a,b a,b,每次可以选择一段区间 [ l , r ] [l,r] [l,r],将 a l 到 a r a_l到a_r al到ar赋值为这段区间的最大值
问是否存在操作序能使得 a a a序列变为 b b b序列,输出 Y E S / N O YES/NO YES/NO
1 ≤ n ≤ 2 e 5 1 \le n \le 2e5 1≤n≤2e5
思路:
对于某个 i i i,有如下情况
1、如果 a i > b i a_i>b_i ai>bi显然不可能
2、如果 a i = b i a_i=b_i ai=bi,可能会影响到其他值
3、如果 a i < b i a_i<b_i ai<bi,考虑 a i a_i ai是利用左边的还是右边的数变成 b i b_i bi的,假设从左边而来
这要求左边必须有一个 x x x使得 a x = b i a_x=b_i ax=bi,并且有
(1) [ x , i ] [x,i] [x,i]区间中 b b b都大于等于 b i b_i bi(不影响左边已经设置好的2、)
(2) [ x , i ] [x,i] [x,i]区间中 a a a都小于等于 b i b_i bi(能够设置成功)
显然离 i i i更近的 x x x一定不会比其他 x x x更劣,所以只要考虑上一次出现就可以了
左边扫一次,右边扫一次记录下第 i i i个位置可不可行就好了
(1)(2)两个限制可以用 S T ST ST表或者线段树简单维护,这里我写的是线段树
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
#define lson t<<1,l,mid
#define rson t<<1|1,mid+1,r
#define ls t<<1
#define rs t<<1|1
const int inf=1e7+5;
const int maxn=2e5+5;
struct Tree
{
int l,r;
int minn,maxx;
}tr[maxn<<2];
int a[maxn],b[maxn];
void pushup(int t)
{
tr[t].maxx=max(tr[ls].maxx,tr[rs].maxx);
tr[t].minn=min(tr[ls].minn,tr[rs].minn);
}
void build(int t,int l,int r)
{
tr[t].l=l;tr[t].r=r;
if(l==r)
{
tr[t].maxx=a[l];
tr[t].minn=b[l];
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(t);
}
int Querymax(int t,int ll,int rr)
{
if(ll<=tr[t].l&&tr[t].r<=rr) return tr[t].maxx;
int mid=(tr[t].l+tr[t].r)>>1;
int res=-inf;
if(ll<=mid) res=max(res,Querymax(ls,ll,rr));
if(rr>mid) res=max(res,Querymax(rs,ll,rr));
return res;
}
int Querymin(int t,int ll,int rr)
{
if(ll<=tr[t].l&&tr[t].r<=rr) return tr[t].minn;
int mid=(tr[t].l+tr[t].r)>>1;
int res=inf;
if(ll<=mid) res=min(res,Querymin(ls,ll,rr));
if(rr>mid) res=min(res,Querymin(rs,ll,rr));
return res;
}
int n;
int las[maxn],nxt[maxn],ans[maxn];
void clean()
{
for(int i=1;i<=n;i++) las[i]=0,nxt[i]=0,ans[i]=false;
}
void work()
{
cin>>n;
rep(i,1,n) cin>>a[i];
rep(i,1,n) cin>>b[i];
build(1,1,n);
//cout<<Querymax(1,1,5)<<"haha\n";
rep(i,1,n)
{
if(a[i]==b[i]) ans[i]=true;
if(!las[b[i]]) {las[a[i]]=i;continue;}
if(Querymin(1,las[b[i]],i)>=b[i]&&Querymax(1,las[b[i]],i)<=b[i]) ans[i]=true;
las[a[i]]=i;
}
for(int i=n;i>=1;i--)
{
if(!nxt[b[i]]) {nxt[a[i]]=i;continue;}
if(Querymin(1,i,nxt[b[i]])>=b[i]&&Querymax(1,i,nxt[b[i]])<=b[i]) ans[i]=true;
nxt[a[i]]=i;
}
rep(i,1,n)
if(!ans[i]) {cout<<"NO\n";clean();return;}
cout<<"YES\n";
clean();
}
int main()
{
cin.tie(0);
cin.sync_with_stdio(0);
int t=1;
cin>>t;
while(t--)
work();
return 0;
}
E(可补)
题目大意:
给定一棵树, q q q个询问
每次询问给定 x , k x,k x,k以及 k k k个点,问删除这 k k k个点以后从 x x x出发的最长简单路径(每次询问后会恢复到一开始的状态)
思路:
很典的DFS序+线段树,但已经一万年没写过数据结构了,之后补,还有虚树的解法我还没有看
F(可补)
题目大意:
给定一棵 n n n 个点的树与 m m m 个限制,每个限制形如 “点 c c c 的权值在 a a a 至 b b b 的路径上最大/小”。试为每个点赋 1 ∼ n 1 \sim n 1∼n 中互不相同的权值,满足所有限制,或判断不存在。 n , m ≤ 2 × 1 0 5 n, m \le 2 \times 10^5 n,m≤2×105。
思路:
赛时:对于这种”权值 i i i大于/小于权值 j j j”的若干条限制,一看就是建图嘛
但是边的数量爆炸了。当时没想到线段树优化建图,想到了也写不出~