首先,不得不提一下的是,感谢出题人ddd大佬的良心(duliu)题目以及十分周到的讲解,收获真的不少。
--------------------------------------------------------------------------------------------------------------------------------------------------
T1:
题目描述
k -NN 是机器学习中常用的算法,其简化版的流程如下:
给出 n 个点 (xi,yi) ,每个点有一个颜色,为红色或黑色。现在给出一个询问位置(x,y) ,这个位置的颜色将由离它最近的 k个点决定:如果离它最近的 k 个点中,红色点的个数大于等于黑色点的个数,那么这个位置为红色,否则为黑色。
Bob 现在得到了这 n 个点,以及询问的位置,他想知道:有多少个 k(1≤k≤n) ,可以使这个询问位置的颜色为红色。
两个点 (x1,y1),(x2,y2) 的距离定义为 sqrt((x1−x2)^2+(y1−y2)^2)。如果询问点到某两个点的距离相同,那么编号较小的视作距离较近。
输入输出格式
输入格式:
第一行三个整数n,x,y ,意义如上所述。
接下来n行,每行三个整数(xi,yi,ci) ,xi,yi 的意义如上所述,ci 表示颜色,ci=1 表示这个点为红色,ci=2 表示这个点为黑色。
输出格式:
一行一个整数,表示答案。
输入样例#1:
3 1 1
1 1 1
2 2 2
3 3 2
输出样例#1:
2
输入样例#2:
3 1 1
1 1 2
2 2 1
3 3 1
输出样例#2:
2
说明
对于 30% 的数据,满足 1≤n≤1000 。
对于100% 的数据,满足1≤n≤10^5,1≤xi,yi,x,y≤10^9 。
比较简单的一题,只需用前缀和维护红与黑的数量关系即可,还有一些细节看代码即可。
需注意的是要记得开longlong,因为10的九次方平方会爆int。
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct point{
ll dis;
int color;
} p[N];
int n, x, y;
ll ans, sum[N];
ll get_dis(ll xi, ll yi) {
return sqrt((x-xi)*(x-xi)+(y-yi)*(y-yi));
}
bool cmp(point p1, point p2) {
if (p1.dis <= p2.dis) return true;
else return false;
}
void init_sum() {
int i;
for (i = 1; i <= n; i++) {
if (p[i].color == 1) sum[i] = sum[i-1]+1;
else sum[i] = sum[i-1]-1;
}
return ;
}
int main() {
int i, c;
ll xi, yi;
scanf("%d%d%d", &n, &x, &y);
for (i = 1; i <= n; i++) {
scanf("%lld%lld%d", &xi, &yi, &c);
p[i].dis = get_dis(xi, yi);
p[i].color = c;
}
sort(p+1, p+n+1, cmp);
init_sum();
for (i = 1; i <= n; i++)
if (sum[i] >= 0) ans++;
printf("%lld", ans);
return 0;
}
T2:
题目描述
回文串指的是正着读和反着读一样的串,比如 "abcba" 和 “dddd” 都是回文串,但 "zzqakioi" 不是。
Bob 现在获得了一个长度为 n 的、由小写字母组成的字符串 s。Alice 给了他 m个转换器,第 i个转换器拥有参数xi,yi,ci ,表示 Bob 可以用这个转换器将任意位置的字母xi 以ci 的代价变成yi 。同时 Alice 还给了他一个超级转换器,表示他可以以 T 的代价,将任意一个字母变为任意另外一个字母。
Bob 对回文串情有独钟,所以他给出了n−k+1 个独立的询问,第 j个询问表示:如果想把 s 中,以 j 开始的、长度为 k 的子串变为回文串,需要的最小代价是多少。Bob 认为回答这个问题太麻烦了,想委托你告诉他这些询问的答案之和是多少。
输入输出格式
输入格式:
第一行四个整数 n, m, k, Tn,m,k,T ,意义如上所述。
接下来一行,为一个长度为 nn 的字符串 ss ,意义如上所述。
接下来 mm 行,每行三个元素 x, y, cx,y,c ,意义如上所述。
输出格式:
一行一个整数,表示答案。
输入输出样例
输入样例#1:
3 0 1 5
dbl
输出样例#1:
0
输入样例#2:
3 0 2 5
ddd
输出样例#2:
0
输入样例#3:
3 0 2 5
dbd
输出样例#3:
10
输入样例#4:
3 1 2 5
dbd
d b 4
输出样例#4:
8
输入样例#5:
3 3 2 5
dbd
d b 4
d a 1
b a 1
输出样例#5:
4
输入样例#6:
3 4 2 5
dbd
d b 4
d a 1
a z 1
b z 1
输出样例#6:
6
说明
对于 30% 的数据,满足1≤n≤2000 。
对于100% 的数据,满足1≤k≤n≤10^5,0≤m≤10^5,1≤T,c≤10^9 。
首先看到题不难想出,我们最终要处理的是两个字母之间的转换,即我们只需想出一个方案,能最快地处理出两个字母之间的最小转换代价即可。而根据题中字母之间的单向转换,不难想出只要连有向边,求最短路即可,这里用的是Floyed,一是方便后面的处理,二是端点较少,只有26个(26个小写英文字母)。一个小技巧是把两点之间的最大值设为t,即超级转换器的代价。这样就处理出了一个dis[i][j]数组,表示i到j之间的最小代价,但实际上如果两个字母组成回文串并不一定是二者彼此转换,这只是一种情况,还有一种情况就是如果两个字母能转移到同一个字母上也可以,其实第一种情况也可看为第二种情况的一种特殊情况(一个转移到自己,另一个转移过来)。用图的语言来说就是如果两点联通或者能够从两个点出发走到同一个点上即成立。所以我们还要用一个minCost数组来合并这两种情况,用Floyed的思想来实现合并即可,详细见代码。
处理完这个后,我们发现在处理询问时还是会超时,因为可以把询问规模看作n,每个询问都需要扫一个长度为n的字符串,这样的复杂度是n^2的,一定会T,那么下面来介绍一种神奇的解法:
观察每组询问中的每一个字母,发现能和它们匹配的字母每次都是隔着一个字母,那么对于每一个字母,我们可以处理出能和它匹配的最左和最右字母,因为中间都是隔一个的,所以只需知道两端即可得到对于一个字母所有询问中匹配的字母,那么可以处理一个前缀和来sum[i][j]表示字母i在1到j出现了多少次,注意这里的j指的是对原字符串进行奇偶分离后的字符串的下标(必须进行分离,否则无法求前缀和),所以最后我们只需把n中的字母扫一遍,对于每一个字母,去查看在它最左端点和最右端点之间匹配点出现的次数(前缀和维护出的sum数组记录),然后次数乘上转换代价计入到总答案即可。
注意要看longlong还有最后ans需除以2,因为是两个字母匹配,而对于这两个字母,我们都会扫到,也就是统计了两遍,而实际上只需统计一遍即可。
由于自己的代码还在调试,所以先贴上std:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n, m, k, T;
char s[MAXN];
int dis[50][50], minCost[50][50];
int sum[30][MAXN];
int getL(int x) {
int res = -1;
res = max(res, x - k + 1);
if(x <= k) res = max(res, 1 + k - x);
return res;
}
int getR(int x) {
int res = n + 1;
res = min(res, x + k - 1);
if(x >= n - k + 1) res = min(res, 2 * n - k + 1 - x);
return res;
}
int getNewPos(int x) {
if(x % 2) return (x + 1) / 2;
else return (n + 1) / 2 + x / 2;
}
int main() {
scanf("%d %d %d %d", &n, &m, &k, &T);
scanf("%s", s + 1);
for(int i = 1; i <= 26; i++) {
for(int j = 1; j <= 26; j++) {
minCost[i][j] = INT_MAX / 2;
if(i == j) dis[i][j] = 0;
else dis[i][j] = T;
}
}
for(int i = 1; i <= m; i++) {
char ch1[3], ch2[3];
int c;
scanf("%s %s %d", ch1, ch2, &c);
int v1 = ch1[0] - 'a' + 1;
int v2 = ch2[0] - 'a' + 1;
dis[v1][v2] = min(dis[v1][v2], c);
}
for(int k = 1; k <= 26; k++) {
for(int i = 1; i <= 26; i++) {
for(int j = 1; j <= 26; j++) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
for(int i = 1; i <= 26; i++) {
for(int j = 1; j <= 26; j++) {
for(int k = 1; k <= 26; k++) {
minCost[i][j] = min(minCost[i][j], dis[i][k] + dis[j][k]);
}
}
}
for(int i = 1; i <= n; i++) {
sum[s[i] - 'a' + 1][getNewPos(i)]++;
}
for(int j = 1; j <= 26; j++) {
for(int i = 1; i <= n; i++) {
sum[j][i] += sum[j][i - 1];
}
}
long long ans = 0;
for(int i = 1; i <= n; i++) {
int l = getL(i), r = getR(i);
l = getNewPos(l), r = getNewPos(r);
for(int j = 1; j <= 26; j++) {
int cnt = sum[j][r] - sum[j][l - 1];
ans += 1LL * cnt * minCost[s[i] - 'a' + 1][j];
}
}
printf("%lld\n", ans / 2);
return 0;
}
T3数学概率题一道,需要自己推导概率式子。