ProblemA: honoka和格点三角形
题目大意: 给你一个 的格点矩阵,请你找出”好三角形“的个数。
解题思路:
可以把面积为 的“好三角形”分为两类分开统计:两条边和两个坐标轴平行;只有一条边和某个坐标轴平行。
对于第一种情况,一定是 或者 的形式,一个 的矩形中含有4个不同的三角形。总数是
对于第二种情况,可以分别统计底边为 2 、高为 1 和底边为 1 、高为 2的情况。要注意底边靠近边界时的特殊讨论。
①对于底边为2,高为1的情况:
若底边和x轴平行,那么底边横向移动(指x轴水平移动,下同)有 种可能,“对点”(指底边相对的点)的某一面选择有 种可能(某一面选择,指的是底边固定的情况,对点在一条直线上移动所做出的选择),而底边纵向移动有 种情况,其中有 种情况对点可以选择两个面,2种情况对点只能选择一个面(当底边移动到格点阵边界的时候)。
因此纵向移动折合为 ,即
若底边和y轴平行,同理可推出
②对于底边为1,高为2的情况,推理方法和上面类似。
示意图:
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
int main()
{
ll n, m;
cin >> n >> m;
ll s = (n - 2) * (m - 1) * 4 % mod + (n - 1) * (m - 2) * 4 % mod; // 两条边分别和x轴和y轴平行的部分
//底部为2,高为1的情况
s = (s + 2 * (n - 1) * (m - 2) % mod * (m - 2) % mod +
2 * (m - 1) * (n - 2) % mod * (n - 2) % mod) % mod;
// 底部为1,高为2的情况
s = (s + 2 * (n - 2) * (m - 1) % mod * (m - 2) % mod +
2 * (m - 2) * (n - 1) % mod * (n - 2) % mod) % mod;
cout << s;
}
ProblemB: kotori和bangdream
题目大意: 签到题计算期望。
解题思路: 直接按照数学期望的定义计算即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, a, b, x;
cin >> n >> x >> a >> b;
int ans = a * x + b * (100 - x);
printf("%.2f\n", ans * n / 100.0);
return 0;
}
Problem C: umi和弓道
题目大意: 现在有一个人在某一个坐标点 上,有 个靶子,现在可以在 轴或者 轴上放置一块挡板,让这个人只能射中 箭,请你求出这块挡板的最小长度。所有点都不在坐标轴上。
解题思路:
我们可以先确定umi所在位置的象限,因为同一象限靶子不可能被挡住。
然后把剩下的点和 连起来,求出该直线与 轴和 轴的交点:
直线方程:
对于 轴上的截距,因为 ,那么截距 的值随便代入一个点就能求出: 。
对于 轴上的截距,我们可以将方程写为 ,这个时候我们知道 ,而 就是直线在 轴上的截距,所以
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<double> v1, v2; // v1、v2分别寸umi和靶子连接的线段与两个坐标轴的交点(如果存在的话)
int main()
{
double x0, y0;
int n, k;
cin >> x0 >> y0 >> n >> k;
k = n - k; // 一共要拦截n - k支箭
for(int i = 0; i < n; ++i) {
double x, y;
cin >> x >> y;
if(x * x0 < 0) { // 与y轴的交点
v2.push_back(y0 - x0 * (y - y0) / (x - x0));
}
if(y * y0 < 0) { // 与x轴的交点
v1.push_back(x0 - y0 * (x - x0) / (y - y0));
}
}
double minv = 1e18;
sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
// 维护x轴挡住n - k个点的挡板长度最小值
if(v1.size() >= k) {
double head = 0, tail = k - 1;
while(tail < v1.size()) {
minv = min(minv, v1[tail] - v1[head]);
tail++, head++;
}
}
// 维护y轴挡住n-k个点的挡板的长度最小值
if(v2.size() >= k) {
double head = 0, tail = k - 1;
while(tail < v2.size()) {
minv = min(minv, v2[tail] - v2[head]);
tail++, head++;
}
}
if(minv == 1e18) cout << -1 << endl;
else cout << fixed << setprecision(7) << minv << endl;
return 0;
}
Problem D: hanayo和米饭
题目大意: 有n个数,分别为 ,现在给你其中 个数,问你剩下的一个数是什么。
解题思路: 使用等差数列求和公式求得n个数的和,然后减去这 个数即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll n, t;
cin >> n;
ll ans = (1 + n) * n / 2;
for(int i = 0; i < n - 1; ++i) {
cin >> t;
ans -= t;
}
cout << ans << endl;
return 0;
}
Problem E: rin和快速迭代
题目大意: 设 为 的因子个数,不停的迭代,每次 的初始值为上一次 的值,问 什么时候收敛到2。
解题思路: 根据质数分解定理,除1以外的任意数都能表示成若干个质数相乘, ,我们根据质数的指数可以进行排列组合,得到 的因数个数为 ,因为这个收敛速度非常快,我们可以直接使用模拟的方法来做。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
using namespace std;
ll num(ll n)
{
ll cnt;
ll res = 1;
for(ll i = 2; i <= n / i; i++){
if(n % i == 0){
cnt = 0;
while(n % i == 0){
n = n / i;
++cnt;
}
res *= (cnt + 1);
}
}
if(n > 1) res *= 2;
return res;
}
int main()
{
ll n;
ll cnt = 0;
cin >> n;
while(true) {
ll tmp = num(n);
cnt++;
if(tmp == 2) break;
n = tmp;
}
cout << cnt << endl;
return 0;
}
Problem F: maki和tree
题目大意: 给你一棵树,树的每个结点上都一种颜色,可能为黑色 ,也可能为白色 ,问你任意两点之间只经过1个黑色点的路径有多少条?
解题思路: 经过一个黑点的路径有两种:两个端点都是白点;其中一个端点是黑点。
因此我们可以先用并查集预处理,将每个白点连通块上的白点个数统计出来。这样我们就可以得知每个黑点所连接的白点的权值(即连通块白点数)。
设某黑点连接了 k 个白点,第 i 个白点的权值为 f(i) 。
那么第一种路径的数量就是 ,而第二种路径只要记录与黑点连接的白色连通块即可,如下图所示:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 +10;
char s[N];
int fa[N], num[N]; //分别代表并查集数组和白色连通块中点的数量
vector<int> G[N];
int Find(int x) {
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
void Union(int x, int y) {
x = Find(x), y = Find(y);
if(x == y) return;
fa[x] = y, num[y] += num[x];
}
int main()
{
int n, x, y;
cin >> n;
cin >> s + 1;
for(int i = 1; i <= n; ++i) {
fa[i] = i;
num[i] = (s[i] == 'W'); // 1代表白色,0代表黑色
}
for(int i = 0; i < n - 1; ++i) {
cin >> x >> y;
G[x].push_back(y), G[y].push_back(x);
/* 预处理白色连通块 */
if(s[x] == 'W' && s[y] == 'W') Union(x, y);
}
ll ans = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == 'W') continue;
ll tmp = 0;
for(int j = 0; j < G[i].size(); ++j)
tmp += num[Find(G[i][j])];
ans += tmp; //黑色为顶点的情况
//白色为端点的情况
for(int j = 0; j < G[i].size(); ++j) {
tmp -= num[Find(G[i][j])];
ans += tmp * num[Find(G[i][j])];
}
}
cout << ans << endl;
return 0;
}
还可以使用dfs或者bfs来处理白色连通块:
bfs:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
vector<int> ve[N];
vector<int> v;
char c[N];
long long sum = 0;
long long dis[N];
/* 统计点x所在的连通块的点的个数 */
int bfs(int x)
{
v.clear();
queue<int> q;
q.push(x);
dis[x] = 1;
int ans = 0;
while(!q.empty())
{
int x = q.front();
q.pop();
ans ++;
v.push_back(x);
for(auto i : ve[x])
{
if(c[i] == 'W' && dis[i] == 0)
{
dis[i] = 1;
q.push(i);
}
}
}
return ans;
}
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> c[i];
for(int i = 1; i < n; i ++)
{
int x, y;
scanf("%d%d", &x, &y);
ve[x].push_back(y);
ve[y].push_back(x);
}
for(int i = 1; i <= n; i ++)
{
if(c[i] == 'W' && !dis[i])
{
int y = bfs(i);
for(auto j : v)
{
dis[j] = y;
}
}
}
for(int i = 1; i <= n; i ++)
{
long long ans = 0;
if(c[i] == 'B')
{
for(auto j : ve[i])
{
if(c[j] == 'W')
{
sum += ans * dis[j] + dis[j];
ans += dis[j];
}
}
}
}
cout << sum << "\n";
return 0;
}
dfs:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> G[100005];
ll sum,ans,t1,t2;
char a[100005];
/* 其实对于每个黑色节点而言,我们可以直接dfs搜索其周围有多少个白色节点连续的路径 */
void dfs(int p,int fa)
{
if(a[p]=='B') return;
sum++;
for(int i=0;i<G[p].size();i++) if(G[p][i]!=fa) dfs(G[p][i],p);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf(" \n%c",&a[i]);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
for(int i=1;i<=n;i++)
{
t1=t2=0;
if(a[i]=='B')
for(int j=0;j<G[i].size();j++)
{
sum=0;
dfs(G[i][j],G[i][j]);
ans+=sum;
t1+=sum;
t2+=sum*sum;
}
ans+=(t1*t1-t2)/2;
}
printf("%lld",ans);
}
Problem G: eli和字符串
题目大意: 现在有一串长度为 的弹幕字符串,里面有3种字符, 记为 分, 记为 分, 记为 分。已经计数过的字符不能重复计数( 可以看做是两个 记为 分,或者一个 记为 分,请你求出最大计分分数。
解题思路: 动态规划,令 表示前 个字符计数的最大值,那么可以得到以下转移方程:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 +10;
ll dp[N];
int main()
{
string s;
int n, a, b, c;
cin >> n >> a >> b >> c;
cin >> s;
for(int i = 0; i < n; ++i) {
if(i) dp[i] = max(dp[i], dp[i-1]); // 当第i个字符为n的时候,显然dp[i] = dp[i-1],因为此时这个n无法和前面的字符串进行组合。
if(i >= 3 && s.substr(i - 3, 4) == "nico")
dp[i] = max(dp[i], dp[i-3] + a);
if(i >= 5 && s.substr(i - 5, 6) == "niconi")
dp[i] = max(dp[i], dp[i - 5] + b);
if(i >= 9 && s.substr(i - 9, 10) == "niconiconi")
dp[i] = max(dp[i], dp[i - 9] + c);
}
cout << dp[n - 1] << endl;
return 0;
}
Problem H: nozomi和字符串
题目大意: 给你一个字符串,请你找出最短的一个至少包含 个相同的某个字母的子串。
解题思路: 使用尺取法,双指针扫一遍即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int dict[256];
int get_max()
{
int maxv = -1;
for(int i = 'a'; i <= 'z'; ++i) {
maxv = max(maxv, dict[i]);
}
return maxv;
}
int main()
{
int n, k;
string s;
cin >> n >> k;
cin >> s;
int l = 0, r = 0;
int ans = INF, sum = 0;
while(true) {
while(r < n && sum < k) {
++dict[s[r++]];
sum = get_max();
}
if(sum < k) break;
// cout << l << " " << r << endl;
ans = min(ans , r - l);
--dict[s[l++]];
sum = get_max();
}
if(ans == INF) ans = -1;
cout << ans << endl;
return 0;
}
Problem I: nico和niconiconi
题目大意: 给你一个01串,至多可以操作 次,每次可以把 变成 或者把 变成 ,在操作后请你找出一个尽可能长的连续子串,子串上的所有字符都相同。
解题思路: 很显然,操作要么全部是把 变成 ,要么是全部把 变成 。我们分两种情况处理,对这两种情况取最大即可,计算的时候用尺取法,维持一个中间字符均为a的子序列,当右端点遇到b时,如果此时还有更改次数,就把b改成a,右端点++;如果此时已经没有了更改次数,就移动左端点,直到左端点越过第一个非a字母(相当于还原更改),然后右端点++。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, k;
string s;
int deal(int a, int b)
{
int l = 0, r = 0;
int ans = 0, cnt = 0;
while(l < n && r < n) {
while(r < n && (s[r] == a || cnt < k)) {
if(s[r] != a) ++cnt;
r++;
}
ans = max(ans, r - l);
while(s[l] == a && l <= r) ++l;
++l;
--cnt;
}
return ans;
}
int main()
{
cin >> n >> k >> s;
cout << max(deal('0', '1'), deal('1', '0')) << endl; //把0改成1和把1改成0两种情况
return 0;
}
Problem J: u’s的影响力
题目大意: 给你一个递推式 ,让你求 。
解题思路:
显然, 可以用 、 、 这三个因子来表示,我们列出其中的一些项,发现规律:
, , ,
,
我们观察到 的幂是满足斐波那契数列的变形。
其中:
和 的幂满足
的幂满足
这样,我们可以将每个幂用矩阵快速幂求出来就行了,但是我们发现,这个幂根本无法存下来,那么,我们想到先对这个幂进行取模,根据费马小定理 ,其中 ,因为 是一个质数,那么我们得到 ,所以我们可以对幂模 。
ps:
比如 ,由费马小定理 ,那么我们可以先对指数模4,得到 ,这里相当于把 拆成了 ,因为 取模后为1,所以
这样,我们将三个幂的矩阵形式求出来:
的幂:
的幂:
的幂:
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int p = 1e9 + 7;
struct M{
LL m[2][2];
};
M mul(M a, M b){
M c;
c.m[0][0] = c.m[0][1] = c.m[1][0] = c.m[1][1] = 0;
for(int i = 0; i < 2; ++ i){
for(int j = 0; j < 2; ++ j){
if(a.m[i][j] == 0) continue;
for(int k = 0; k < 2; ++ k){
c.m[i][k] = (c.m[i][k] + a.m[i][j] * b.m[j][k] % (p - 1)) % (p - 1);
}
}
}
return c;
}
LL Mpow(LL n){
M a, k;
a.m[0][0] = a.m[0][1] = a.m[1][0] = 1; a.m[1][1] = 0;
k.m[0][0] = k.m[1][1] = 1; k.m[0][1] = k.m[1][0] = 0;
while(n){
if(n & 1) k = mul(a, k); a= mul(a, a); n >>= 1;
}
return k.m[0][1] % (p - 1);
}
LL fpow(LL a, LL b){
a %= p;
LL ans = 1;
while(b){
if(b & 1) ans = ans * a % p; a = a * a % p; b >>= 1;
}
return ans;
}
int main(){
LL n, x, y, a, b, k;
cin >> n >> x >> y >> a >> b;
k = fpow(a, b);
if(n == 1) cout << x % p;
else if(n == 2) cout << y % p;
else if(n == 3) cout << ((x % p) * (y % p) % p) * k % p;
else{
x = fpow(x, Mpow(n - 2) + p - 1);
y = fpow(y, Mpow(n - 1) + p - 1);
k = fpow(k ,Mpow(n) - 1 + p - 1);
cout << (x * y % p) * k % p;
}
return 0;
}