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 龍
首先我们可以找到所有全是零的区间,手摸就可以发现这两操作是两种
- 花费 a,将两个全零的区间合并
- 花费 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}\)
那么外切的条件就是
内切的条件是
题目很坑的一点是两个完全相同的圆是不能被算作内切的,完全相同的条件就是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
有
再来考虑d!=t
情况,可以拆分为
这里的 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";
}
}
}