前言
感觉这场的难度应该是div4才对,确实太简单了()
一、A. DBMB and the Array
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,s,x;
cin>>n>>s>>x;
vector<int>a(n+1);
int sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
if(sum>s)
{
NO;
}
if((s-sum)%x==0)
{
YES;
}
NO;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先,因为 x 是大于 0 的,所以如果初始的累加和就大于 s 那就必然不可能。之后,因为每次都加上 x,所以若目标累加和和初始累加和的差是 x 的倍数就可以达成,否则就不行。
二、B. Reverse a Permutation
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>p(n+1);
for(int i=1;i<=n;i++)
{
cin>>p[i];
}
int left=0;
for(int i=1;i<=n;i++)
{
if(p[i]!=n-i+1)
{
left=i;
break;
}
}
if(left==0)
{
for(int i=1;i<=n;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
return ;
}
int right=n+1;
for(int i=left;i<=n;i++)
{
if(p[i]==n-left+1)
{
right=i;
break;
}
}
reverse(p.begin()+left,p.begin()+right+1);
for(int i=1;i<=n;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
return ;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题直接贪心即可,选中第一个在倒序排列中的数和当前数不同的位置,然后在后续找到这个数,把这个数交换过来即可。
三、C. Replace and Sum
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,q;
cin>>n>>q;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
vector<int>suf(n+2);
for(int i=n;i>=1;i--)
{
suf[i]=max(suf[i+1],max(a[i],b[i]));
}
vector<ll>pre(n+1);
for(int i=1;i<=n;i++)
{
pre[i]=pre[i-1]+suf[i];
}
int l,r;
while(q--)
{
cin>>l>>r;
cout<<pre[r]-pre[l-1]<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为替换成后一个位置的数,所以肯定是可以把每个位置都刷成后缀最大值的,那么就是算上 b 数组统计一下后缀最大值,在统计一下后缀最大值的前缀和,这样就能实现范围查询了。
四、D. Monster Game
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
sort(a.begin()+1,a.end());
vector<ll>pre(n+1);
for(int i=1;i<=n;i++)
{
pre[i]=pre[i-1]+b[i];
}
ll ans=0;
for(int i=1,j=n;i<=n;i++)
{
ll x=a[i];
while(j>=1&&pre[j]>n-i+1)
{
j--;
}
ans=max(ans,x*j);
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
是不是一般这种题都和单调性强相关()
观察可以发现,若当前难度 x 中能杀 k 个怪兽,那么难度增大后能杀的数量肯定更少。之后又能发现,在对剑的强度从小到大排序后,对于相邻的两把剑 a 和 b,其实是不用枚举 a 和 b 中间的难度的,因为必然劣于选 b 难度。所以,只需要维护两个指针分别指向当前强度的剑和当前最多能杀到的怪兽,然后每次选当前剑的强度为难度,去移动另一个指针即可。那么就只需要用前缀和维护一下杀到第 i 个怪兽需要的剑的数量即可。
五、E. Product Queries
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>dp(n+1,INF);
for(int i=1;i<=n;i++)
{
dp[a[i]]=1;
}
for(ll i=1;i<=n;i++)
{
for(ll j=2;j*j<=i;j++)
{
if(i%j!=0)
{
continue;
}
ll x=i/j;
dp[i]=min(dp[i],dp[j]+dp[x]);
}
}
for(int i=1;i<=n;i++)
{
cout<<(dp[i]==INF?-1:dp[i])<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为可以重复选,那么不难发现,若 2 和 3 都能选出来,那么 6 一定可以选出来。所以可以直接定义 dp[i] 为乘出 i 的最小元素个数,那么首先出现过的数的 dp 必然是 1,之后每次考虑其所有因子,取最小值即可。
其实可以用筛法把复杂度从这个方法的根号降到 logn 的,但反正能过就这样吧()
六、F. Pizza Delivery
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,Ax,Ay,Bx,By;
cin>>n>>Ax>>Ay>>Bx>>By;
vector<pii>p(n+1);
for(int i=1;i<=n;i++)
{
cin>>p[i].first;
}
for(int i=1;i<=n;i++)
{
cin>>p[i].second;
}
sort(p.begin()+1,p.end());
vector<int>X;
vector<vector<int>>Y;
X.push_back(Ax);
Y.push_back({Ay});
for(int i=1;i<=n;i++)
{
X.push_back(p[i].first);
Y.push_back({});
int ni=i;
while(ni<=n&&p[ni].first==p[i].first)
{
Y.back().push_back(p[ni].second);
ni++;
}
i=ni-1;
}
X.push_back(Bx);
Y.push_back({By});
int m=X.size();
//dp[i]表示当前来到第i个横坐标,送完一遍在最上方和最下方的最小行程
vector<vector<ll>>dp(m,vector<ll>(2));
int x=Ax;
dp[0][0]=dp[0][1]=0;
for(int i=1;i<m;i++)
{
ll need=abs(x-X[i])+abs(Y[i].back()-Y[i][0]);
dp[i][1]=need+min(dp[i-1][0]+abs(Y[i-1].back()-Y[i].back()),dp[i-1][1]+abs(Y[i-1][0]-Y[i].back()));
dp[i][0]=need+min(dp[i-1][0]+abs(Y[i-1].back()-Y[i][0]),dp[i-1][1]+abs(Y[i-1][0]-Y[i][0]));
x=X[i];
}
cout<<dp[m-1][0]<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
又是状态机 dp !!
首先,由于是不能往左走,那么就只能像扫描线一样从左往右扫,每次先把当前横坐标的货全送完再往后走。之后,不难看出,对于当前横坐标的所有货,其会排成一条竖线,那么要想走的距离最小,就必然是从一端开始往另一端跑一遍。
所以,在根据横坐标分组以后,可以考虑定义 dp[i][0] 为当前来到第 i 组,送完之后在最上方的最小距离,dp[i][1] 为送完之后在最下方的最小距离。那么每次计算出当前这条线的长度和从上一组走到下一组横坐标的变化,然后讨论转移即可。
七、G. Paths in a Tree
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
int ask(int u,int v)
{
cout<<"? "<<u<<" "<<v<<endl;
int ans;
cin>>ans;
return ans;
}
void solve()
{
int n;
cin>>n;
vector<vector<int>>g(n+1);
for(int i=1,u,v;i<=n-1;i++)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int>dfn(n+1);
int fill=0;
auto dfs=[&](auto &&self,int u,int fa)->void
{
dfn[++fill]=u;
for(auto v:g[u])
{
if(v!=fa)
{
self(self,v,u);
}
}
};
dfs(dfs,1,0);
for(int i=1;i<fill;i+=2)
{
int u=dfn[i];
int v=dfn[i+1];
if(ask(u,v)==1)
{
if(ask(u,u)==1)
{
cout<<"! "<<u<<endl;
}
else
{
cout<<"! "<<v<<endl;
}
return ;
}
}
cout<<"! "<<dfn[fill]<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为最多问二分之 n 次,又因为每次查询可以问两个点,那么肯定考虑将所有点分成二分之 n 对,然后一对一对问。
对于具体的划分方法和询问过程,可以考虑先求出整棵树的 dfn 序,然后按 dfn 序的顺序,相邻的顶点两两一组问。那么若这两个顶点之间有边,那么若回答是1,就说明两点至少有一个在 x 到 y 的路径上,那么就只需要再单独问一次 u,如果是1就直接输出 u,否则就输出 v 即可。 而如果这两顶点之间没有边,根据 dfn 序的性质,前面的所有点肯定都被问过了,能来到这个位置肯定是因为之前的回答全是0,即没有点被经过。那么若此时的回答是1,就同样说明 u 和 v 两点至少经过一个,所以也可以再问一遍直接输出即可。
总结
H确实有点难补不大动了......