新生水平测试赛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";
            }
        }
    }
}
posted @ 2026-03-07 18:06  randnameaaa  阅读(45)  评论(0)    收藏  举报