2022河南萌新联赛第(七)场

C 机智的我

一开始有选中的概率是\(\frac{1}{n}\),打开 k 个后的概率是\(\frac{n-1}{n}\times\frac{1}{n-k-1}\)

所以\(k=0\)没差别,\(k>0\)更换一定更好

#include<bits/stdc++.h>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}


int32_t main() {
   int n = read() , k = read();
   if( k == 0 ) cout << "Whatever\n";
   else cout << "Why not\n";
   return 0;
}

H 防风台

二分答案

首先二分出答案,然后吧小于二分值的全部都选上,看看时候可以挡住所有行或列即可

#include<bits/stdc++.h>

using namespace std;



int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1005;
int a[N][N] , r[N] , c[N] , n , m;
vector<int> ve;

bool check( int x ){
    memset( r , 0 , sizeof( r ) );
    memset( c , 0 , sizeof( c ) );
    int cnt = 0;
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 1 ; j <= m ; j ++ ){
            if( a[i][j] <= x ){
                if( r[i] == 0 ) r[i] = 1 , cnt ++;
                if( c[j] == 0 ) c[j] = 1 , cnt ++;
            }
        }
    return cnt == n + m;
}

int32_t main() {
    n = read() , m = read();
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 1 ; j <= m ; j ++ )
            a[i][j] = read() ;
    int l = 0 , r = 1e9 , mid , res;
    while( l <= r ){
        mid = ( l + r ) >> 1;
        if( check( mid ) )  res = mid , r = mid - 1 ;
        else l = mid + 1;
    }
    cout << res << "\n";
    return 0;
}

贪心

每一行取最小值,每一列也取最小值。然后最小值在取最大值。

#include<bits/stdc++.h>
using namespace std;

const int N = 1005;
int a[N][N] , res = INT_MIN;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

int32_t main() {
    int n = read() , m = read();
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 1 ; j <= m ; j ++ )
            a[i][j] = read();
    for( int i = 1 ; i <= n ; i ++ ){
        int t = INT_MAX;
        for( int j = 1 ; j <= m ; j ++ )
            t = min( t , a[i][j] );
        res = max( res , t );
    }
    for( int j = 1 ; j <= m ; j ++ ){
        int t = INT_MAX;
        for( int i = 1 ; i <= n ; i ++ )
            t = min( t , a[i][j] );
        res = max( res , t );
    }
    cout << res << "\n";
}

G 小明不希望太简单

把字母和字母的个数放入一个大根堆中,每次拿出最多的和次多的,然后输出即可。

注意处理一些细节问题。

#include<bits/stdc++.h>

using namespace std;

string s;
int cnt[30];
priority_queue< pair<int,int> > q;

int32_t main() {
    cin >> s;
    for( char it : s )
        cnt[ it - 'a' ] ++;
    for( int i = 0 ; i < 26 ; i ++ )
        if( cnt[i] > 0 )
            q.push( { cnt[i] , 'a'+i } );

    while( q.size() ){
        auto [ v , k ] = q.top(); q.pop();
        cout << char(k) , v --;
        if( v == 0 ) continue;
        if( q.empty() ) break;

        auto [sv , sk ] = q.top(); q.pop();// 取出次多的字母
        cout << char(sk) ,  sv --;
        q.push( { v , k } );
        if( sv ) q.push( { sv , sk } );
    }
    return 0;
}

J 最短路

这道题因为询问次数很少,所以可以离线做。

按照边权从小到大加边,如果加入当前边后某个询问的两点连通,那么当前边的权值就是该询问的答案。

#include<bits/stdc++.h>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e5+5;
int fa[N] , n , m , a[N] , b[N] , res[N] , q , cnt ;

struct Edge{
    int u , v , w;
    Edge( int u , int v , int w ) : u(u) , v(v) , w(w) {};
    friend bool  operator < ( Edge a , Edge b ){
        return a.w < b.w;
    }
};

vector<Edge> e;

int getFa( int x ){
    if( fa[x] == x ) return x;
    return fa[x] = getFa( fa[x] );
}

void merge( int u , int v ){
    u = getFa(u) , v = getFa(v);
    fa[v] = u;
}

int32_t main() {
    n = read() , m = read();
    for(  int u , v , w ; m ; m -- )
        u = read() , v = read() , w = read() , e.push_back( { u , v , w } );
    sort( e.begin() , e.end() );
    q = read();
    for( int i = 1 ; i <= q ; i ++ )
        a[i] = read() , b[i] = read() , res[i] = -1;
    for( int i = 1 ; i <= q ; i ++ )
        if( a[i] == b[i] ) res[i] = 0 , cnt ++;
    for( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
    for( auto [ u , v , w ] : e ){
        merge( u , v );
        for( int i = 1 ; i <= q ; i ++ ){
            if( res[i] != -1 ) continue;
            if(getFa( a[i] ) == getFa(b[i] ) ) res[i] = w , cnt ++ ;
        }
        if( cnt == q ) break;
    }
    for( int i = 1 ; i <= q ; i ++ )
        cout << res[i] << '\n';
}

B 龍

首先我们可以找到所有全是零的区间,手摸就可以发现这两操作是两种

  1. 花费 a,将两个全零的区间合并
  2. 花费 b,删掉一个全零的区间

然后发现操作顺序是不会影响最终的答案的,并且每个区间的长度、端点都是无用信息罢了。

所以只要枚举一下合并多少区间就可以计算出需要删除多少区间,然后就能计算出花费,找到最小的花费就好了。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e3+5;
string s;

void solve(){
    int n = read() , a = read() , b = read() , x = read() , m = 0 , res = INT_MIN;
    cin >> s;
    m = (s[0] == '0');
    for( int i = 1 ; i < n ; i ++ )
        m += ( s[i] == '0' && s[i-1] == '1' );
    if( m == 0 ) {
        cout << "Yes\n" << x << "\n";
        return;
    }
    for( int i = 0 ; i < m ; i ++ )
        if( a * i + ( m - i) * b <= x ) res = max( res , x - (a * i + ( m - i) * b) );
    if( res == INT_MIN ) cout << "No\n";
    else cout << "Yes\n" << res << "\n";
    return;
}

int32_t main() {
    for( int T = read() ; T ; T -- )
        solve();
}

L ___O_o

这道题算是很简单的题目了,但是数据非常的搞人

首先两个圆的圆心是\((\frac{a}{b},\frac{1}{2b^2}),(\frac{c}{d},\frac{1}{2d^2})\),半径是\(\frac{1}{2b^2},\frac{1}{2d^2}\)

那么外切的条件就是

\[\sqrt{(\frac{a}{b}-\frac{c}{d})^2+(\frac{1}{2b^2}-\frac{1}{2d^2})^2}=\frac{1}{2b^2}+\frac{1}{2d^2}\\ (\frac{a}{b}-\frac{c}{d})^2+(\frac{1}{2b^2}-\frac{1}{2d^2})^2=(\frac{1}{2b^2}+\frac{1}{2d^2})^2\\ (\frac{a}{b}-\frac{c}{d})^2+ (\frac{1}{2b^2})^2+(\frac{1}{2d^2})^2-\frac{1}{2b^2d^2}=(\frac{1}{2b^2})^2+(\frac{1}{2d^2})^2+\frac{1}{2b^2d^2}\\ (\frac{a}{b}-\frac{c}{d})^2=\frac{1}{b^2d^2}\\ |\frac{a}{b}-\frac{c}{d}|=\frac{1}{|bd|}\\ |ad-cb|=1 \]

内切的条件是

\[\sqrt{(\frac{a}{b}-\frac{c}{d})^2+(\frac{1}{2b^2}-\frac{1}{2d^2})}=\frac{1}{2b^2}-\frac{1}{2d^2}\\ (\frac{a}{b}-\frac{c}{d})^2+(\frac{1}{2b^2}-\frac{1}{2d^2})^2=(\frac{1}{2b^2}-\frac{1}{2d^2})^2\\ \frac{a}{b}-\frac{c}{d}=0\\ ad=bc \]

题目很坑的一点是两个完全相同的圆是不能被算作内切的,完全相同的条件就是a==c&&b==d

剩下就很简单了

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e3+5;

void solve(){
    int a = read() , b = read() , c = read() , d = read();
    if( a == c && b == d ) cout << "NO\n";
    else if( abs( a*d - c*b ) == 1 || a * d == c * b ) cout << "YES\n";
    else cout << "NO\n";
}

int32_t main() {
    for( int T = read() ; T ; T -- )
        solve();
}

F 数对

这道题是一个蛮简单的思维题,对于任意的两个数的最高位,如果俩个数的最高位的1 在一个位置上,那么与就是 1,而异或就是0。反之,异或的最高位是 1,与的最高位是 0。

可以发现两个数对的关系仅仅与最高位的一所在的位置有关。我们可以预处理出所有数的最高位的 1 的位置。然后用前缀和维护一下区间的情况,结合组合数就可以算出结果。

值得注意的是两个 0 是复合条件的特殊情况。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e5+5;
int a[35][N];

int get( int x ){ // 计算最高位的 1 所在的位置
    if( x == 0 ) return 0;
    return __lg(x) + 1;
}

int32_t main() {
    int n = read() , m = read();
    for( int i = 1 , x ; i <= n ; i ++ ){
        x = read();
        for( int j = 0 ; j <= 31 ; j ++ ) a[j][i] = a[j][i-1];
        a[get(x)][i] ++;
    }
    for( int l , r , res ; m ; m -- ){
         l = read() , r = read() , res = 0;
         for( int i = 0 , t ; i <= 31 ; i ++ )//枚举区间中最高的1在i的个数
            t = a[i][r] - a[i][l-1] , res += t * ( t - 1 ) / 2;// 组合数
         cout << res << "\n";
    }
}

A 构造?

一道复杂的枚举题?

首先枚举一下序列的长度n,然后再枚举一下开头的第一个数字t,在枚举一下[2,n]中的 d 的个数i

此时满足当前条件的序列个数是\(C_{n-1}^{i}\times9^{n-i-1}\)

因为过程中会反复的用到组合数和次方,可以先递推出所有组合数和 9 点次方存在数组c[i][j],p[i]

for( int n = l ; n <= r ; n ++ )
    for( int t = 1 ; t <= 9 ; t ++ )
        for( int i = 0 ; i <= n-1 ; i ++ )
            res += t * c[n-1][i] * p[n-i-1] * i;

但是这样忽略了一个问题就是如果t==d的情况,所以代码要订正为

for( int n = l ; n <= r ; n ++ )
    for( int t = 1 ; t <= 9 ; t ++ )
        for( int i = 0 ; i <= n-1 ; i ++ ){
            int cnt = i + ( d == t );
            int sum = c[n-1][i] * p[n-i-1];
            res += t * cnt * sum;
        }

这样的代码就是正确的,但是这样直接上交会TLE,需要优化

先来考虑d==0此时是不会出现d==t的情况的,所以对于已经确定的长度n

\[\sum_{t=1}^9\sum_{i=0}^{n-1} t\times C_{n-1}^i\times9^{n-i-1}\times cnt=\sum_{t=1}^9t\times cnt \times \sum_{i=0}^{n-1} C_{n-1}^i\times9^{n-i-1} \\= 45cnt\sum_{i=0}^{n-1} C_{n-1}^i\times9^{n-i-1} \]

再来考虑d!=t情况,可以拆分为

\[(45-d) \sum_{i=0}^{n-1} C_{n-1}^i\times9^{n-i-1} 9 \times cnt+ d \sum_{i=0}^{n-1} C_{n-1}^i\times9^{n-i-1}\times (cnt+1)\\ =(45\times cnt + d) \sum_{i=0}^{n-1} C_{n-1}^i\times9^{n-i-1} \]

这里的 cnt和 i实际上是一个值

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 105 , mod = 1e9+7;
int c[N][N] , p[N];

void solve(){
    int l = read() , r = read() , d = read() , res = 0;
    if( d == 0 ){
        for( int n = l ; n <= r ; n ++ ){
            int cnt , sum;
            for( int i = 0 ; i <= n-1 ; i ++ ){
                sum = c[n-1][i] * p[ n - i - 1 ] % mod;
                res = ( res + 45 * i % mod * sum % mod ) % mod;
            }
        }
    }
    else {
        for( int n = l ; n <= r ; n ++ ){
            int cnt , sum;
            for( int i = 0 ; i <= n-1 ; i ++ ){
                sum = c[n-1][i] * p[ n - i - 1 ] % mod;
                res = ( res + (45*i+d) % mod * sum % mod ) % mod;
            }
        }
    }
    cout << res << "\n";
}
int32_t main() {
    c[0][0] = c[1][1] = c[1][0] = 1;
    for( int i = 2 ; i <= 100 ; i ++ ){
        c[i][0] = 1;
        for( int j = 1 ; j <= i ; j ++ )
            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
    }
    p[0] = 1;
    for( int i = 1 ; i <= 100 ; i ++ ) p[i] = p[i-1] * 9 % mod;

    for( int T = read() ; T ; T -- )
        solve();


}

I 计算几何

看似是一道计算几何罢了。

对于求不规则多边形的面积实际上就是定积分,但是这里并不需定积分。

把图形放在边长为\(0.01\times0.01\)的网格图中,枚举每一个格子,并判断是否在四个椭圆内即可。

赛事没出这道题属实我大傻逼了,精度没有控制好哎~~

#include<bits/stdc++.h>
using namespace std;

double a , b , res;

void solve(){
    cin >> a >> b , res = 0;
    for (double i = -10; i <= 10; i += 0.01)
        for (double j = -10; j <= 10; j += 0.01) {
            if ((i * i) / (a * a) + (j * j) / (b * b) > 1) continue;
            if ((i * i) / (b * b) + (j * j) / (a * a) > 1) continue;
            if (i * i + j * j + i * j > a) continue;
            if (i * i + j * j - i * j > b) continue;
            res += 0.0001;
        }
    printf("%.0lf\n", res);
}
int32_t main() {
    int T ; cin >> T;
    for( ; T ; T -- )
        solve();
}

K 工程师CY

用一个堆来维护当前搭建到的最左侧的位置,然后从最左侧开始尽可能的链接到当前位置,在链接的过程中记录一下最大的跨度即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

typedef pair<int,int> PII;
priority_queue< PII , vector<PII> , greater<PII> > q;


int32_t main() {
    int n = read() , k = read() , res = INT_MIN;
    vector<int> a(n+5);
    for( int i = 1 ; i <= n ; i ++ ) a[i] = read();
    q.push( { 0 , k } ) , a[n+1] = k;
    for( int i = 1 ; i <= n + 1 ; i ++ ){
        int cnt = a[i];
        while( q.size() && cnt ){
            auto [ k , v ] = q.top() ; q.pop();
            if( k == i ) break;
            res = max( res , i - k );
            if( v > cnt ) {
                q.push({k, v - cnt});
                cnt = 0;
                break;
            }
            else cnt -= v;
        }
        if( cnt != a[i] ) q.push( { i , a[i] - cnt } ); // i 处放了a[i] - cnt
    }
    cout << res << "\n";
}

D 疯狂星期八

f[i][j]表示前i个物品吃了j个的最小花费。

然后因为要乘i的原因,所以如果选定吃那些食物的情况下,从后向前吃一定更优

for(int i = n; i >= 1 ; i -- )
    for(int j = 100 ; j >= 1 ; j -- )
        f[j] = min(f[j], f[j - 1] + a[i] + (j - 1) * i);

然后剩下就是01背包的常规优化

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read(){
	int x = 0 , f = 1 , ch = getchar();
	while( ( ch < '0' || ch > '9' ) && ch != '-' ) ch = getchar();
	if( ch == '-' ) f = -1 , ch = getchar();
	while( ch >= '0' && ch <= '9' ) x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar();
	return x * f;
}

const int N = 1e5+5;
int a[N] , f[105];

int32_t main(){
	int n = read() , m = read();
	for( int i = 1 ; i <= n ; i ++ ) a[i] = read();

	fill( f , f + 105 , INT_MAX ) , f[0] = 0;
	for( int i = n ; i >= 1 ; i -- )
		for( int j = 100 ; j >= 1 ; j -- )
			f[j] = min( f[j] , f[j-1] + a[i] + ( j - 1 ) * i );
	for( int i = 100 ; i >= 0 ; i -- )
		if( f[i] <= m ){
			cout << i;
			return 0;
		}
}

E 墨笔钨丝函数

\(f(x) = 2^{sum1}+3^{sum2}\),\(sum1,sum2\)分别是质因数分解后奇次方个数和偶次方个数。因为\(a_i\)只有300,所以最多只有62个质因子,然后相乘对于奇偶的改变可以用异或来表示,这样就把每个数转化成一个bitset<62>,每次求进行区间修改,求区间异或和。

对于区间的操作,这里我选择的是分块,因为分块很好写,代码几乎就是模板。并且截止到目前,我写的分块是跑的最快的算法。好吧实际上是写了一个多小时的线段树写不出来,才写的分块。写这个分块大概只用了15min,还是很好写的。

#include <bits/stdc++.h>
#define int long long
using namespace std;

typedef bitset<62> T;
vector prime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
             107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,
             227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293};
const int N = 2e5+5 , mod = 998244753;

struct Part{
    int l , r ;
    T sum , tag;
    vector<T> val;
    Part( int l , int r , T sum , T tag , vector<T> val ) :
            l(l) , r(r) , sum(sum) , tag(tag) , val(val){};
};

int pos[N] , n , m , t ;
vector< Part > parts;

T div( int x ){
    T ans;
    for( int i = 0 , cnt ; i < 62 ; i ++ ){
        cnt = 0;
        while( x % prime[i] == 0 ) cnt ++ , x /= prime[i];
        ans[i] = cnt & 1;
    }
    return ans;
}

int power( int x , int y ){
    int ans = 1;
    while( y ) {
        if( y & 1 ) ans = ans * x % mod;
        y >>= 1;
        x = x * x % mod;
    }
    return ans;
}

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

void update( int l , int r , T d ){
    for( int i = pos[l] ; i <= pos[r] ; i ++ ){
        if( parts[i].l >= l && parts[i].r <= r ){
            parts[i].tag ^= d;
            if( parts[i].val.size() & 1 ) parts[i].sum ^= d;
        }
        else{
            for( int j = max( l , parts[i].l ) - parts[i].l ; j <= min( r , parts[i].r ) - parts[i].l ; j ++ )
                parts[i].sum ^= d , parts[i].val[j] ^= d ;
        }

    }
}

T getSum( int l , int r ){
    T sum;
    for( int i = pos[l] ; i <= pos[r] ; i ++ ){
        if( parts[i].l >= l && parts[i].r <= r ) sum ^= parts[i].sum ;
        else{
            for( int j = max( l , parts[i].l ) - parts[i].l ; j <= min( r , parts[i].r ) - parts[i].l ; j ++ )
                sum ^= parts[i].tag ^ parts[i].val[j] ;
        }
    }
    return sum;
}
int32_t main(){
    n = read() , m = read() , t = sqrt(n);
    for( int i = 1 ; i <= t ; i ++ )
        parts.push_back( { (i-1)*t+1 , i * t , T() , T() , vector<T>() } );
    if( parts.back().r < n ) parts.push_back( { parts.back().r + 1 , n , T() , T() , vector<T>() } );

    for( int i = 1 , j = 0 , x ; i <= n ; i ++ ){
        x = read();
        T val = div(x);
        if( i > parts[j].r ) j ++;
        parts[j].sum ^= val , parts[j].val.push_back(val) , pos[i] = j;
    }
    for( int op , l , r , d ; m ; m -- ){
        op = read() , l = read() , r = read();
        if( op == 1 ) {
            d = read() ;
            T v = div(d);
            update( l , r , v );
        }
        else{
            T v = getSum( l , r ) ;
            int sum2 = v.count() , sum1 = v.size() - sum2;
            cout << power( 2 , sum1 ) * power( 3 , sum2 ) % mod << "\n";
        }
    }
}
posted @ 2022-08-21 22:04  PHarr  阅读(468)  评论(0编辑  收藏  举报