2022寒假训练week5
Day1
AcWing 1913.公平摄影
首先我们考虑全是H
或G
的情况其实很简单就是遍历就好
然后就是把H
赋值为1
,G
赋值为 -1
,然后做前缀和若s[l]==s[r]
则证明[l+1,r]
中的H,G
是一样多的,因为我们要找最长的我们只要保存s[i]
每个值第一次出现的位置就行,剩下就是数据范围比较大需要离散化
#include <bits/stdc++.h>
#define F first
#define S second
using namespace std;
const int N = 1e5+5;
int n , sum , res;
unordered_map<int,int>pos;
pair<int,int> cow[N];
int main()
{
cin >> n;
pos[0] = 0;
char x;
for( int i = 1 ; i <= n ; i ++ )
{
cin >> cow[i].F >> x ;
cow[i].S = ( x == 'G' ? -1 : 1 );
}
sort( cow + 1 , cow + 1 + n );
int last = cow[1].F;
for( int i = 1 ; i <= n ; i ++ )
{
sum += cow[i].S;
if( pos.count(s[i]) )
res = max( res , cow[i].F - cow[ pos[s[i]]+1 ].F );
else pos[s[i]] = i;
if( cow[i].S == cow[i-1].S )
res = max( res , cow[i].F - last );
else last = cow[i].F;
}
cout << res << endl;
}
AcWing1875. 贝茜的报复
记录每个字母数奇偶数的个数,因为只要考虑结果的奇偶性所以枚举每个数奇偶就行,奇数用1
偶数用2
,然后(B+I+S+S+E+E)*(G+O+E+S)*(M+O+O)
的奇偶性和(B+I)*(G+O+E+S)*(M)
相同可以简化一点运算,最后的复杂度是\(2^7\)
#include <bits/stdc++.h>
#define F first
#define ll long long
#define S second
using namespace std;
const int N = 1e5+5;
const string mp = "BESIGOM";
int n , cnt , sum ;
unordered_map<char , int > p;
unordered_map<char , pair<int,int> > h;
void dfs( int t , int r )
{
if( t == 7 )
{
sum = (p['B']+ p['I'] )*(p['G']+p['O']+p['E']+p['S'])*(p['M']);
if( sum % 2 == 0 ) cnt += r ;
return ;
}
p[ mp[t] ] = 1 , dfs( t + 1 , r * h[ mp[t] ].F );
p[ mp[t] ] = 2 , dfs( t + 1 , r * h[ mp[t] ].S );
}
int main()
{
for( int i = 0 ; i < 7 ; i ++ ) h[ mp[i] ].F = h[ mp[i] ].S = 0;
cin >> n;
char ch;
for( int x ; n ; n -- )
{
cin >> ch >> x;
if( x&1 ) h[ch].F++;
else h[ch].S++;
}
dfs( 0 , 1);
cout << cnt << endl;
}
Day2,3
22牛客寒假训练营4
A R
无论怎么取,我们都都不能取P
,所以先把字符串按照P
分隔。
对于一每一段字符串,将R
转化为1
其他的转化成0
,然后做一个前缀和。然后我们枚举左端点,二分出符合条件最靠左的右端点,从这个端点向右的点都可以的
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 2e5+5;
int b[N] , ed[N];
ll n , k , m , res;
string s;
int main()
{
cin >> n >> k >> s;
for( int i = 1 ; i <= n ; i ++ )
{
if( s[i-1] == 'P' ) b[i] = 0 , ed[i] = i;
else b[i] = b[i-1] + ( s[i-1] == 'R' );
}
ed[n+1] = n+1;
for( int i = n ; i >= 1 ; i -- )
{
if( ed[i] ) continue;
ed[i] = ed[i+1];
}
for( int i = 1 , l , r , mid , ans ; i <= n ; i ++ )
{
if( b[ed[i]-1] - b[i-1] < k ) continue;
l = i , r = ed[i]-1 , ans = -1;
while( l <= r )
{
mid = ( l + r ) >> 1 ;
if( b[mid] - b[i-1] >= k ) ans = mid , r = mid - 1;
else l = mid + 1;
}
res += ed[i] - ans;
}
cout << res << endl;
}
C 蓝彗星
用两个查封数组来分别表示蓝红,然后前缀和并枚举每一个点就行
#include <bits/stdc++.h>
#define ll long long
#define F first
#define S second
using namespace std;
const int N = 1e5+5;
int n , t , a[2*N] , b[N*2] , m , res;
string s;
inline int read()
{
int x = 0 , ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch =getchar();
return x;
}
int main()
{
n = read() , t = read();
cin >> s;
for( int i = 1 , x ; i <= n ; i ++ )
{
x = read();
m = max( m , x );
if( s[i-1] == 'B' ) a[x] ++ , a[x+t]--;
else b[x] ++ , b[x+t] --;
}
for( int i = 1 ; i < m + t ; i ++ )
{
a[i] += a[i-1] , b[i] += b[i-1];
if( a[i] > 0 && b[i] == 0 ) res ++;
}
cout << res << endl;
}
D 雪色光晕
对于每次给定两个点,用两点式得到直线方程,然后转发成点斜式。点到直线的最短距离式做垂线,所以通过斜率得到另一个直线的斜率,然后用斜率和点得到垂线的解出交点判断交点是否在线段中。
但是点斜式有特列就是斜率为零和斜率不存在,如果不在线段上就要算到两个端点的距离并取最小值。在比赛中没有考虑点在线段上这种情况导致没有过
import math
n = int(input())
a,b,x,y = map(float, input().split(' '))
res = math.sqrt( (x-a)**2 + (y-b)**2 )
for i in range(0 , n):
xi ,yi = map( int , input().split(' ') )
c = a + xi
d = b + yi
res = min( res , math.sqrt((x-c)**2 + (y-d)**2 ) )
if yi == 0 :
if (x>=min(a,c) and x<= max(a,c) ):
res = min( res , abs(y-b))
elif xi == 0 :
if (y>=min(b,d) and y <= max(b,d)):
res = min( res , abs(x-a))
else:
k1 = (b-d)/(a-c)
b1 = b-k1*a
k2 = -1.0/k1
b2 = y-k2*x
xd = (b1-b2)/(k2-k1)
if(xd >= min(a,c) and xd <= max(a,c) ):
yd = k1*xd+b1
res = min( res , math.sqrt( (x-xd)**2 + (y-yd)**2 ) )
a=c
b=d
print(res)
E 真假签到题
把代码在本地一跑就找到规律了,直接输出就好
#include<bits/stdc++.h>
using namespace std;
int main()
{
ll x;
cin >> x;
cout << x << endl;
}
F小红的记谱法
纯纯大模拟,我比赛的时候没读完结果反着模拟了一边,测试样例的时候才发现写反了
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int b[N] , tp , nw;
char a[N];
string s , e ="6712345";
int main()
{
cin >> s;
for( int i = 0 ; i < s.size() ; i ++ )
{
if( s[i] >= 'A' && s[i] <= 'G' ) a[++tp] = e[s[i] - 'A'] , b[tp] = nw;
else if( s[i] == '<' ) nw --;
else nw ++;
}
for( int i = 1 ; i <= tp ; i ++ )
{
cout << a[i];
while( b[i] < 0 ) printf(".") , b[i] ++;
while( b[i] > 0 ) printf("*") , b[i] --;
}
return 0;
}
H真真真真签到题
确实很签到,就是给半个体对角线
import math
x = int(input())
y = x * x * 4 / 3
y = math.sqrt(y)
y = y ** 3
print(y)
J 区间合数的最小公倍数
首先用线性筛筛出所有的素数,将剩下的合数每一个都质因数分解,并且所有保存下每一个素数的最高次幂。最后把所有素数的最高次幂加起来。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 30005 , mod = 1000000007;
int l , r , m , po[N];
ll res = 1;
bool notprime[N] , f;
vector<int> prime;
ll power( ll x , ll y )
{
ll ans = 1 % mod;
for( ; y ; y >>= 1 )
{
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
}
return ans;
}
inline void primes( int n )
{
for( int i = 2 ; i <= n ; i ++ )
{
if( !notprime[i] ) prime.push_back(i);
for( auto it : prime )
{
if( it * i > n ) break;
notprime[ i * it ] = 1;
if( i % it == 0 ) break;
}
}
}
int main()
{
cin >> l >> r;
primes(r);
m = prime.size();
for( int i = l , t ; i <= r ; i ++ )
{
if( !notprime[i] ) continue;
f = 1 , t = i;
for( int j = 0 , cnt ; j < m ; j ++ )
{
if( t % prime[j] ) continue;
cnt = 0;
while( t % prime[j] == 0 ) cnt ++ , t /= prime[j];
po[j] = max( po[j] , cnt );
}
}
if(!f)
{
cout << "-1\n";
return 0;
}
for( int i = 0 ; i < m ; i ++ )
res = res * power( prime[i] , po[i] ) % mod;
cout << res << endl;
}
Day4,5
22牛客寒假训练营5
D 数位小孩
首先我们找出0到9所有和为素数的组合,然后按照这个组合用dfs枚举就一定能满足条件一,然后dfs的过程中统计顺便有没有一就可以满足条件二,至于条件三,只要从高位开始枚举,就可以满足。不断枚举,不断判断是否在[l,r]
中,只要在就计数器加一
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
int ln , rn;
ll l , r , res ;
bool pri[] = {0,0,1,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1};
vector<int> p[10];
void dfs( ll num , int la , bool flag)
{
if( num > r ) return ;
if( num >= l && flag ) res ++;
ll cur = num * 10;
if( cur >= r ) return ;
for( auto it : p[ la ] ) dfs( cur + it , it , flag || (it==1) );
return ;
}
int main()
{
for( int i = 0 ; i <= 9 ; i ++ )
for( int j = 0 ; j <= 9 ; j ++ )
if( pri[i+j] ) p[i].push_back(j);
cin >> l >> r;
ln = log10(l)+1 , rn=log10(r)+1;
for( int i = 1 ; i <= 9 ; i ++ )
dfs( i , i , ( i == 1 ) );
cout << res << endl;
return 0;
}
G 163小孩
#include<bits/stdc++.h>
using namespace std;
int cnt;
void dfs( int x , int sum )
{
if( x == 14 )
{
cnt += ( sum == 6 );
return ;
}
for( int i = 0 ; i <= 4 ; i ++ )
dfs( x + 1 , sum + i );
return ;
}
int main()
{
dfs( 1 , 0 );
cout << cnt << endl;
return 0;
}
虽然这个程序会TLE
但是,可以直接在本地跑出来然后输出
print("18395")
然后我们可以也可以用数学方法算出来
首先假设用字母代表不同数字,
abcdef
aabcde
aabbcd
aaabcd
aaabbc
aaabbb
aaaabc
aaaabb
所以答案就是\(C_{13}^6+C_{13}^5C_6^1+C_{13}^4C_4^1C_3^1+C_{13}^4C_4^1+C_{13}^3C_3^1C_2^1+C_{13}^2+C_{13}^3C_3^1+C_{13}^2C_2^1\)
这个具体做法可以参考下图24点的做法来理解
I 兔崽小孩
按照区间进行分段,并把区间排序求前缀和
对于每一个询问二年分出最小大于k
的区间,用前缀和快速求出所有的和判断即可
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 1e6+5;
int n , q , m , k , p;
int a[N] , b[N];
inline int read()
{
int x = 0 , ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar();
return x;
}
void slove()
{
k = read() , p = read();
int t = upper_bound( a + 1 , a+1+m , k ) - a;
if( t > m )
{
if(p) printf("No\n");
else printf("Yes\n");
return ;
}
int cnt = b[m] - b[t-1] - (m-t+1)*k;
if( cnt >= p ) printf("Yes\n");
else printf("No\n");
}
int main()
{
n = read() , q = read() , m = n-1;
int t0 = read();
for( int i = 2 , t1 ; i <= n ; i ++ )
t1 = read() , a[i-1]= t1 - t0 , t0=t1;
sort( a + 1 , a + 1 + m );
for( int i = 1 ; i <= m ; i ++ )
b[i] = a[i] + b[i-1];
while(q--) slove();
return 0;
}
J 三国小孩
我的牌多一定赢
n , m , k = map(int , input().split(' '))
if n + m > k:
print("YES")
else :
print("NO")
Day6,7
22牛客寒假训练营6
D 删除子序列
贪心的删掉最靠前的即可
#include <bits/stdc++.h>
#define ll long long
#define F first
#define S second
#define T (t[i]-'a')
using namespace std;
const int N = 2e5+5;
int cnt , n , m;
queue<int>q[30];
string s , t;
void slove()
{
cin >> n >> m >> s >> t;
cnt = 0;
for( int i = 0 ; i < 26 ; i ++ )
while( q[i].size() )q[i].pop();
for( int i = 0 ; i < n ; i ++ )
q[s[i]-'a'].push(i);
for( int l = -1 ; ; l = -1 )
{
for( int i = 0 ; i < m ; i ++ )
{
while( q[T].size() && q[T].front() <= l ) q[T].pop();
if(q[T].size()) l = q[T].front() , q[T].pop();
else
{
cout << cnt << "\n";
return ;
}
}
cnt ++;
}
}
int main()
{
int ts;
cin>>ts;
while(ts--)slove();
return 0;
}
E 骑士
所有人都不能被其他人秒杀可以转化成,攻击力最高的不能被次高的秒杀,其他人不能被攻击力最高的人秒杀
#include <bits/stdc++.h>
#define ll long long
#define F first
#define S second
using namespace std;
const int N = 2e5+5;
ll k , n , now , last , id;
ll res;
pair<ll,ll> w[N];
void slove()
{
scanf("%lld" , &n ) , res = 0;
for( ll i = 1 , x , y , z ; i <= n ; i ++ )
{
scanf("%lld%lld%lld" , &x , &y , &z );
w[i].F = x , w[i].S = y + z;
}
sort( w + 1 , w + 1 + n , greater< pair<ll,ll> >() );
for( int i = 2 ; i <= n ; i ++ )
if( w[1].F >= w[i].S )
res += w[1].F - w[i].S + 1;
if( w[2].F >= w[1].S )
res += w[2].F - w[1].S + 1;
printf("%lld\n" , res );
}
int main()
{
int t;
scanf("%d",&t);
while(t--)slove();
return 0;
}
F +-串
统计下加减号的数量,一次操作可以让绝对值减二
import math
t = int(input())
while t > 0:
t -= 1
s = input()
k = int(input())
a = s.count('+')
b = len(s)-a
c = abs(a-b)
if c >= 2*k:
c -= k*2
print(c)
else :
k -= c//2
if ( c%2 == 1):
print(1)
else:
print((k%2)*2)
I A+B问题
高精度的模板题目
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int k , a[N] , b[N] , n , m ;
string s;
int main()
{
cin >> k;
cin >> s;
n = s.size();
reverse(s.begin(),s.end());
for( int i = 1 ; i <= n ; i ++ ) a[i] = s[i-1] - '0';
cin >> s;
m = s.size();
reverse(s.begin(),s.end());
for( int i = 1 ; i <= m ; i ++ ) b[i] = s[i-1] - '0';
n = max( n , m );
for( int i = 1 ; i <= n ; i ++ )
{
a[i] += b[i];
while( a[i] >= k ) a[i+1]++ , a[i] -= k;
}
while( a[n+1] ) n ++;
for( int i = n;i>=1;i--) cout << a[i];
cout << endl;
}
J 牛妹的数学难题
看似很复杂的问题,实际上因为a
的范围只有0,1,2
变得很简单
若是有0
则一定是零,然后枚举2
的数量,用组合数计算出方案数即可,这里的取模中有除法可以直接用费马小定理得到逆元
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 10000005 , mod = 998244353;
ll n , k , one , two;
ll p[N] , res , tp , cur ;
inline int read()
{
int x = 0 , ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar();
return x;
}
inline ll power( ll x , ll y )
{
ll ans = 1;
for( ; y ; y >>= 1 )
{
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
}
return ans;
}
inline ll invs( ll x )
{
return power(x , mod - 2 );
}
inline ll C( ll x , ll y )
{
return p[y] * invs( p[y-x] ) % mod * invs( p[x] ) % mod ;
}
int main()
{
n = read() , k = read();
for( int i = 1 , x ; i <= n ; i ++ )
{
x = read();
if( x == 1 ) one ++;
if( x == 2 ) two ++;
}
if( one + two < k )
{
cout << 0 << endl;
return 0;
}
p[0] = 1;
for( ll i = 1 ; i <= max( one , two ) ; i ++ )
p[i] = p[i-1] * i % mod;
cur = max( 0LL , k - one );
tp = power( 2 , cur );
for( ll i = cur ; i <= min( two , k ) ; i ++ )
{
res = ( res + C( i , two ) * C( k - i , one ) % mod * tp ) % mod;
tp = tp * 2 % mod;
}
cout << res << endl;
}