A. Balanced Substring
题目
一个只有’a’,'b’的串,求一个子串使得 a的个数和b相等
思路
略
AC代码
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 100 ,mod=1e9 + 7;
char str[N];
int n;
int sa[N],sb[N];
void solve()
{
cin>>n>>str+1;
for(int i=1;i<=n;i++)
{
if(str[i]=='a') sa[i] = sa[i-1]+1,sb[i]=sb[i-1]+0;
else sa[i]=sa[i-1],sb[i]=sb[i-1]+1;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
int a = sa[j]-sa[i-1],b=sb[j]-sb[i-1];
if(a==b)
{
cout<<i<<' '<<j<<endl;
return;
}
}
cout<<"-1 -1\n";
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
B. Chess Tournament
题目
n个人对局,每个人都和其他人斗一场,有胜,平,败三种可能。有两种人
- 不败就行
- 至少赢一次
求一种对局方案,满足所有人需求。
思路
- 对第一种人,不能败,但如果他赢,对方案的构造有害无益(其他人会输,他赢,但他又不需要赢),所以对第一类的贪心策略就是全平
- 对第二种人,要赢一次,他只能找同类人赢(第一类不败)而且赢一次就行,其他的输了就行。
很简单的贪心思路,主要代码实现有点麻烦。详见代码注释。
AC代码
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 10 + 50, mod = 1e9 + 7;
int n;
char s[N];
char m[N][N];
void solve()
{
cin >> n >> s + 1;
// 初始化
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
m[i][j] = '.';
for (int i = 1; i <= n; i++)
m[i][i] = 'X';
// 处理第一类 全平,对称更新矩阵
for (int i = 1; i <= n; i++)
if (s[i] == '1')
{
for (int j = 1; j <= n; j++)
{
if (m[i][j] == '.')
m[j][i] = m[i][j] = '=';
}
}
// 处理第二类,给赢得打个标记
bool win[N];
memset(win, 0, sizeof win);
for (int i = 1; i <= n; i++)
{
if (s[i] == '1')
continue;
if (!win[i])
{
int j = 1;
while (j <= n && m[i][j] != '.')// 找到第一个未处理的场次
j++;
if (m[i][j] == '.')
{
m[i][j] = '+', m[j][i] = '-';
win[i] = 1;
}
else break;// 如果没有未处理的 说明没法满足要求break
}
}
// 判不可行
for (int i = 1; i <= n; i++)
{
if (s[i] == '2' && !win[i])
{
cout << "NO\n";
return;
}
}
// 让其他未处理的场都平局就行(处理成胜败也可)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(m[i][j]=='.')m[i][j]='=';
puts("YES");
for (int i = 1; i <= n; i++, puts(""))
for (int j = 1; j <= n; j++)
cout << m[i][j];
}
signed main()
{
ios::sync_with_stdio();
cin.tie();
cout.tie();
int T;
cin >> T;
while (T--)
solve();
return 0;
}
C. Jury Meeting
题目
n个人发言,每个人都可以分一个次数,轮流发言,次数用尽就跳过,求有多少的分配排列可以使没有一个人连续发言。没有输出0.
思路
- 先考虑怎样的次数序列总不合法,对于序列最大值 m a x max max,如果它很大显然会有人连续发言,现在找到这个“大的界限”。
- 因为每一轮都会使发言次数减一,并且如果连续发言,此时一定只有一个次数非零的人,也就是说只要有一个人能充当“间隔” , 就不会使得人连续发言。
- 因此考虑最大值
a
a
a和第二大值
b
b
b的关系。
- 当 a = b a=b a=b时,显然在最后会是它们的交替,所有排列都合法。
- 当 a − b > 1 a-b>1 a−b>1时,总会出现连续,此时答案就为0
- 当 a − b = 1 a-b=1 a−b=1时,必须至少有一个b排在a的后面,详见下例。
/*
2
1 2
1 2 -> 122 ->no
2 1 -> 212 ->yes
*/
- 因此,现在只需要解决如何求最大值右边至少有一个次大值的排列数即可。
设次大值有 t o t tot tot个,考虑这 t o t + 1 tot+1 tot+1个数,右边一个次大值都没的可能性是 1 1 + t o t \frac{1}{1+tot} 1+tot1 ,所求概率为 t o t 1 + t o t \frac{tot}{1+tot} 1+tottot.
所以全排列乘上这个概率就行。
AC代码
时间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define debug(x) cout<<"> "<< x<<endl;
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=998244353;
int n;
int a[N];
ll p[N];
ll ksm(ll _a,ll b)
{
ll res = 1 ;
while(b)
{
if(b&1) res=(res*_a)%mod;
_a=(_a*_a)%mod;
b>>=1;
// debug(res)
}
return res%mod;
}
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
int tot = count(a+1,a+1+n,a[n]);
if(tot>=2)
cout<<p[n]<<endl;
else
{
tot = count(a+1,a+1+n,a[n]-1);
if(tot==0)
puts("0");
else
{
// tot/(tot+1) * x;
cout<<1ll*p[n]*tot%mod*ksm(tot+1,mod-2) %mod<<endl;
}
}
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
p[0]=1;
for(int i=1;i<N;i++) p[i] = p[i-1]*i%mod;
int T;cin>>T;
while(T--)
solve();
return 0;
}
后记
取模古古怪怪,要专门看看安全取模的技巧。还有排列组合,也感觉懵逼的