新生水平测试赛1 题解
难度区间:800-1800
A-CF1873E
注意到如果高度\(h\)不可行,则高度\(h+1\)一定不可行。所以可以二分答案。对于给定的高度,\(O(n)\)枚举一边计算出用水量,与给定\(x\)比较即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,x,h[N];
inline bool check(int k){
int res=0;
for(int i=1;i<=n;i++)
if(k>=h[i]) res+=k-h[i];
return res<=x;
}
signed main()
{
cin>>t;
while(t--){
cin>>n>>x;
for(int i=1;i<=n;i++)
cin>>h[i];
int l=0,r=1e12,bst=-1;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) bst=mid,l=mid+1;
else r=mid-1;
}
cout<<bst<<"\n";
}
}
B-CF1528A
贪心直觉告诉我们,为了让差的绝对值最大,每个节点取的值必定是边界值(要么是 \(l_i\),要么是 \(r_i\)),绝不可能是中间的值。
对每个结点\(u\),定义两个状态:
-
dp[u][0]表示结点\(u\)取左边界\(l_i\)时,以\(u\)为根的子树所获取的最大得分 -
dp[u][0]表示结点\(u\)取右边界\(r_i\)时,以\(u\)为根的子树所获取的最大得分
进行状态转移即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,c[N][2];
int he[N],ne[N<<1],go[N<<1],tot;
inline void add(int a,int b){
ne[++tot]=he[a];he[a]=tot;go[tot]=b;
}
inline void dfs(vector<vector<int>> &dp,int u,int p){
for(int i=he[u];i;i=ne[i]){
int v=go[i];
if(v==p) continue;
dfs(dp,v,u);
dp[u][0]+=max(dp[v][0]+abs(c[v][0]-c[u][0]),dp[v][1]+abs(c[v][1]-c[u][0]));
dp[u][1]+=max(dp[v][0]+abs(c[v][0]-c[u][1]),dp[v][1]+abs(c[v][1]-c[u][1]));
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>t;
while(t--){
cin>>n;tot=0;
for(int i=1;i<=n;i++) he[i]=0;
for(int i=1;i<=n;i++)
cin>>c[i][0]>>c[i][1];
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
vector<vector<int>>dp(n+1,vector<int>(2,0));
dfs(dp,1,0);
cout<<max(dp[1][0],dp[1][1])<<"\n";
}
}
C-CF1833E
把每个人看作点,每个记忆关系看作边,这样就得到了一张图。这张图包含了若干个连通块,对于每个连通块,只会有两种状态:
-
闭合的完整的环
-
链状结构
对于最多的情况,显然是连通块总数;
对于最少的情况,不难发现所有的链状结构可以首尾相连,拼成一个巨大的圈,而已经闭合的环则不行。所以最少的情况就是环的总数加上\(x\),其中如果有链状结构,\(x=1\),否则 \(x=0\)。
(我写得比较乱,就不放代码了())
D-CF1559D1
注意到\(N\leq 1000\),我们可以暴力枚举点对。如果两点在两个森林中都不连通,就加上这条边,并更新森林。连通性用并查集维护即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,m1,m2;
int fa1[N],fa2[N];
int g[1005][1005];
inline int find(int fa[],int x){
if(x!=fa[x]) return fa[x]=find(fa,fa[x]);
return x;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m1>>m2;
for(int i=1;i<=n;i++)
fa1[i]=fa2[i]=i;
for(int i=1;i<=m1;i++){
int u,v;cin>>u>>v;
g[u][v]=g[v][u]=1;
int A=find(fa1,u);
int B=find(fa1,v);
if(A!=B)fa1[A]=B;
}
for(int i=1;i<=m2;i++){
int u,v;cin>>u>>v;
g[u][v]=g[v][u]=1;
int A=find(fa2,u);
int B=find(fa2,v);
if(A!=B)fa2[A]=B;
}
int ans=0;
vector<pair<int,int> >v;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j&&!g[i][j]){
int A=find(fa1,i);
int B=find(fa1,j);
int C=find(fa2,i);
int D=find(fa2,j);
if(A==B||C==D) continue;
ans++;fa1[A]=B;fa2[C]=D;
g[i][j]=g[j][i]=1;
v.push_back({i,j});
}
cout<<ans<<"\n";
for(auto [u,v]:v) cout<<u<<" "<<v<<"\n";
}
E-CF1433D
如果所有城市的黑帮编号都相同,那么显然无法完成任务;
否则,我们可以取城市1为根,并把所有与城市1黑帮编号不相同的城市与城市1连接起来,剩下的与城市1黑帮编号相同的城市随便与其他编号不同的城市连接起来即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,a[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
bool f=1;
for(int i=1;i<n;i++)
if(a[i]!=a[i+1]) f=0;
if(f) cout<<"NO\n";
else{
cout<<"YES\n";
vector<int>v;
int pos;
for(int i=2;i<=n;i++)
if(a[i]!=a[1]) pos=i,cout<<1<<" "<<i<<"\n";
else v.push_back(i);
for(auto c:v) cout<<pos<<" "<<c<<"\n";
}
}
}
F-签到题
直接输出\(n\%k\)即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n,k;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>t;
while(t--){
cin>>n>>k;
cout<<n%k<<"\n";
}
}
G-CF2136D
设我们要求的坐标为\((x_0,y_0)\)。观察到锚点的坐标范围在\(10^9\),我们考虑当机器人横纵坐标移动到大于\(10^9\)的位置的时候,会发生什么(向右,向上各移动2e9的距离,此时机器人的位置在\((x_0+2e9,y_0+2e9)\)。
此时,离机器人最近的锚点一定是确定的,因为此时距离在横纵方向可以拆分成四段,这里我们考虑\(x\)轴方向,这个方向的距离会被分成两段,即锚点横坐标到\(x=1e9\)的距离+\(x=1e9\)到机器人横坐标的距离。后者是固定的,所以我们只用找前者最小的锚点即可。加上纵坐标,我们就要考虑\(1e9-y_{锚点}+1e9-x_{锚点}\)最小的锚点,而由于我们知道锚点的坐标,我们就能知道此时机器人离哪个锚点最近。
然后我们有了这个距离\(R\),它等于\(1e9-y_{锚点}+1e9-x_{锚点}\)加上\(x_0+2e9-1e9+y_0+2e9-1e9\),由此我们就能够得出\(x_0+y_0\)的值。
同理,我们再把机器人挪到左上角(向左移动\(4e9\)的距离),再用同种方法我们能够求出\(y_0-x_0\)。这样就得到机器人初始坐标的值了。
如果不好理解,可以自己画一个坐标轴,原点的坐标为\((1e9,1e9)\),机器人在右上角,锚点在左下角。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n;
struct node{
int x,y;
}a[105];
signed main()
{
cin>>t;
while(t--){
cin>>n;
int minn1=1e18,minn2=1e18;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
if(2e9-a[i].x-a[i].y<minn1)
minn1=2e9-a[i].x-a[i].y;
if((a[i].x+1e9)+(1e9-a[i].y)<minn2)
minn2=2e9+a[i].x-a[i].y;
}
printf("? R 1000000000\n");
fflush(stdout);
int k;
cin>>k;
printf("? R 1000000000\n");
fflush(stdout);
cin>>k;
printf("? U 1000000000\n");
fflush(stdout);
cin>>k;
printf("? U 1000000000\n");
fflush(stdout);
cin>>k;
int res1=k-minn1-2e9;//x0+y0
printf("? L 1000000000\n");
fflush(stdout);
cin>>k;
printf("? L 1000000000\n");
fflush(stdout);
cin>>k;
printf("? L 1000000000\n");
fflush(stdout);
cin>>k;
printf("? L 1000000000\n");
fflush(stdout);
cin>>k;
int res2=k-minn2-2e9;//y0-x0
int y=(res1+res2)/2,x=res1-y;
printf("! %lld %lld\n",x,y);
}
}
H-CF1157E
为了让字典序最小,我们要从左到右贪心地让每个\(c_i\)尽可能接近0。对于当前的\(a_i\),最理想的\(b_i\) 应该是\(n−a_i\)。
我们可以把数组b的所有元素扔进一个multiset中。每次处理\(a_i\)时,直接在集合里lower_bound查找大于等于\(n−a_i\)的最小值。如果找到了,这就拼出了当前能得到的最小的\(c_i\)(产生进位);如果没找到,说明只能被迫接受没有产生进位的情况,那就取集合中最小的元素(即 begin())。每次用完一个数字,记得将其从集合中删除掉。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,a[N],b[N];
multiset<int>s;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i],s.insert(b[i]);
for(int i=1;i<=n;i++){
auto pos=s.lower_bound(n-a[i]);
if(pos==s.end()) cout<<(a[i]+*s.begin())%n<<" ",s.erase(s.begin());
else cout<<(a[i]+*pos)%n<<" ",s.erase(pos);
}
}
I-CF1899C
线性dp。定义一个状态\(dp[i]\),表示以第\(i\)个数结尾的所有满足条件子数组中最大的和。对于每一位\(i\),它可以自己成为一个子数组,和为\(a[i]\)。如果它的奇偶性和前一个数不同的话,它也可以与之前的最大数组合并,和为\(dp[i-1]+a[i]\)。最后找到\(dp\)数组里面最大的值即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,a[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
vector<int>dp(n+1,0);
dp[1]=a[1];
for(int i=2;i<=n;i++)
if((a[i]+a[i-1])%2) dp[i]=max(a[i],dp[i-1]+a[i]);
else dp[i]=a[i];
int ans=-1e18;
for(int i=1;i<=n;i++)
ans=max(ans,dp[i]);
cout<<ans<<"\n";
}
}
J-CF1829E
求最大连通块模板题。遍历整个网格,每遇到一个深度大于0且未被访问过的格子,就启动一次DFS或BFS。在搜索过程中,把当前连通块内所有的深度累加起来,并给走过的格子打上“已访问”标记。每次搜索结束后更新最大值即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int t,n,m,res=0;
int a[N][N];
bool st[N][N];
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
void dfs(int x,int y){
res+=a[x][y];
st[x][y]=1;
for(int i=0;i<4;i++){
int fx=x+dx[i],fy=y+dy[i];
if(fx>0&&fx<=n&&fy>0&&fy<=m&&!st[fx][fy]&&a[fx][fy])
dfs(fx,fy);
}
}
int main() {
cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
int ans=0;
memset(st,0,sizeof st);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(!st[i][j]&&a[i][j]>0){
dfs(i,j);
ans=max(ans,res);
res=0;
}
cout<<ans<<std::endl;
}
}
K-CF276C
要想总和最大,显然应该把最大的数字放在被查询次数最多的位置上。那怎么快速知道每个位置被查询了多少次呢?这就是差分的用武之地:对于每个查询区间 \([l,r]\),在差分数组\(l\)处加 1,\(r+1\)处减 1。全部标记完后求一次前缀和,就能得到每个下标的“查询频次”。最后将频次数组和原数组分别排序,对应相乘求和即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,q,a[N],c[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=q;i++){
int l,r;cin>>l>>r;
c[l]++;c[r+1]--;
}
for(int i=1;i<=n;i++)
c[i]+=c[i-1];
sort(c+1,c+n+1);
int ans=0;
for(int i=1;i<=n;i++)
ans+=c[i]*a[i];
cout<<ans;
}
L-CF580D
看到 \(n\leq 18\) 这个极小的数据范围,就是状压dp的强烈信号。
由于有“先后顺序”带来的额外加成,我们需要知道当前吃了哪些菜,以及最后吃的是哪道菜。
定义状态 dp[mask][i]:mask 是一个二进制数(比如 01011 表示吃了第 0, 1, 3 道菜),i 表示吃下的最后一道菜是 i。
转移时,我们可以枚举当前以及吃了的菜,并枚举另一道已经吃过了的菜来进行转移。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n,m,k,a[20],c[20][20];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>k;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=1;i<=k;i++){
int x,y;cin>>x>>y;
cin>>c[x-1][y-1];
}
vector<vector<int>>dp(1e6+5,vector<int>(n));
for(int i=0;i<n;i++)
dp[1<<i][i]=a[i];//先吃第一道菜
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++)
if((i>>j)&1){
for(int k=0;k<n;k++)
if(((i>>k)&1)&&j!=k)
dp[i][j]=max(dp[i][j],dp[i^(1<<j)][k]+c[k][j]+a[j]);
}
}
int ans=0;
for(int i=0;i<(1<<n);i++)
if(__builtin_popcount(i)==m){//这个函数可以快速获取一个数二进制表示中1的个数
for(int j=0;j<n;j++)
ans=max(ans,dp[i][j]);
}
cout<<ans;
}
M-CF1370C
对于每个输入,我们考虑奇偶性:
-若\(n\)为奇数,那么除非\(n=1\),先手无法操作必败,否则先手都可以直接除以\(n\)来获得胜利;
-若\(n\)为偶数,我们考虑这个数除去因子2之后剩下的数\(x\)。如果\(x\)是1,那么先手只能进行-1操作把\(n\)变成奇数,此时后手可直接获胜(n=2例外,需要特判);如果这个数包含1个因子2,那么此时先手不能直接除以\(x\),因为这会给对手留1个2,这样自己就输了。所以要判断\(x\)是否为质数。如果\(x\)为质数,那么先手只能直接除以\(x\),必败;否则可以给对方留下一个奇数因子,这样自己就必胜了;如果包含多个因子2,那么先手只需要除以\(x\)就行,对方会遇到这段话开始的时候的情况,先手必胜。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5,mod=1000000007;
int t,n;
inline bool check(int x){
for(int i=2;i<=sqrt(x);i++)
if(n%i==0) return 0;
return 1;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>t;
while(t--){
cin>>n;
if(n&1) {
if(n==1) cout<<"FastestFinger\n";
else cout<<"Ashishgup\n";
}else{
if(n==2) cout<<"Ashishgup\n";
else{
int res=0;
while(n%2==0){
n/=2;
res++;
}
if(n==1||(res==1&&check(n))) cout<<"FastestFinger\n";
else cout<<"Ashishgup\n";
}
}
}
}

浙公网安备 33010602011771号