SDKD 2019 Spring Training Series C3 10th Round 题解

博客介绍了多道算法题,包括FZU - 1001、CodeForces - 705A等。涵盖数组遍历、字符串输出、快速幂、二分枚举、分类讨论、计算几何、AC自动机等算法知识,给出各题难度、定位、题目大意及解决办法,还提及部分题目的坑点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem A - Duplicate Pair  FZU - 1001

难度:★☆☆☆☆

定位:数组遍历签到题

题目大意:输入1~n-1,共有n个数,其中有且仅有一个重复的数字,找到其并输出。注意多组样例!

#include <iostream>
#include <cstring>
using namespace std;

const int maxn = 1000000 + 10;
bool mark[maxn];

int main()
{
	ios::sync_with_stdio(false);
	int n;
	while (cin >> n){
                memset(mark, 0, sizeof(mark));
		for (int i = 0, x; i < n; i ++){
			cin >> x;
                        if (mark[x]){
				cout << x << endl;
                        }
                        mark[x] = true;
		}
	}
	return 0;
}

Problem B - Hulk CodeForces - 705A

难度:★☆☆☆☆

定位:字符串输出签到题 

题目大意:轮流输出“I hate”,“I love”,不过要注意中间的间隔词汇变化,以及空格的处理。

#include <iostream>
#include <string>
using namespace std;

int main()
{
        int n;
        cin >> n;
        int cnt = 0;
        while (n --){
                if (cnt)	cout << "that ";
                if (cnt & 1)	cout << "I love ";
                else 		cout << "I hate ";
                cnt ++;
        }
        cout << "it" << endl;
	return 0;
}

Problem C - A Math Problem HDU - 6182

难度:★☆☆☆☆

定位:快速幂(或者细心的朋友可能发现,可以使用预处理暴力过去)

题目大意:问在正整数中,有多少个正整数“k”的k次方,是小于等于n的

解决办法:

  • 方法一,快速幂,防止多次循环求幂超时,注意16的本身次幂会溢出,这是一个坑点;
  • 方法二,预处理出来15以及15之前所有正整数的本身次方,存储在数组中进行暴力即可,因为16的本身次幂已经超过了10^18大小。
#include <iostream>
using namespace std;
#define LL long long

LL pow_mod(LL n, LL a){
	LL ans = 1;
	while (a){
                if (a & 1){
                        ans *= n;
                }
                n *= n;
                a >>= 1;
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	LL n;
	while (cin >> n){
                int ans = 0;
                LL base = 1;
                while (pow_mod(base, base) <= n && base <= 15){
                        ans ++;
                        base ++;
                }
                cout << ans << endl;
	}
	return 0;
}

Problem D - Cable master POJ - 1064

难度:★★☆☆☆

定位:二分枚举上限

题目大意:电缆提供商提供了n条电缆,但是为了保证比赛的公正性,现在你要将他们切割成k段长的相等的电缆。可以浪费,不过不可以拼接,问每段最大的长度是多少。

解决办法:看似题目冗长,实则在阅读理解背后的算法原理很简单。二分枚举切割后每段的长度,如果能够切割足够的k段,那么就向上收缩二分上界,否则向下收缩二分上界。这样可以大大降低复杂度,这也是一道求“多种不同值中找最大相同值”的非常经典的二分枚举上界的题目。

#include <iostream>
#include <iomanip>
#include <cstdio>
using namespace std;

const int maxn = 10000 + 10;
double a[maxn];

int n, k;
bool ok(double x){
	int cnt = 0;
	for (int i = 0; i < n; i ++){
                cnt += (int)(a[i] / x);
	}
	return cnt >= k;
}

int main()
{
	scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i ++){
		scanf("%lf", &a[i]);
        }
        double l = 0.0, r = (double)(1e8);
        double mid;
        int cnt = 0;

        while (cnt < 100){
		mid = (l+r) / 2.0;

                if (ok(mid)){
			l = mid;
                }
                else {
			r = mid;
                }
		cnt ++;
        }
        cout << setprecision(2) << fixed << ((double)((int)(mid*100))) / 100.0 << endl;
	return 0;
}

Problem E - Barcelonian Distance CodeForces - 1078A

难度:★★☆☆☆

定位:分类讨论

题目大意:给出一个在二维平面直角坐标系第一象限内的,单位长度为1的无限大网格,每条直线都代表道路。又给你一条直线 ax+by+c=0,也代表一条道路。求两点之间的最短距离。

解决办法:找到以两个点连线为对角线的矩形,并将其四条边无限延长,跟另外一条直线,会产生两个(直线水平或竖直)或者四个交点(直线倾斜),计算出这个些点,然后算出所有可能的走法的长度即可。注意题目精度为1e-10。

#include <iostream>
#include <cmath>
#include <cstdio>
#include <iomanip>
using namespace std;
#define eps 1e-10
#define sq(x) ((x)*(x))

struct Point {
	double x, y;
	Point() {}
	Point(double x, double y): x(x), y(y) {}
}	p1, p2, q1, q2, q3, q4;


inline double dis(const Point& a, const Point& b) {
	return sqrt(sq(a.x-b.x) + sq(a.y-b.y));
}
inline double min(double a, double b, double c, double d, double e){
	double ans = a;
	ans = min(ans, b);
	ans = min(ans, c);
	ans = min(ans, d);
	ans = min(ans, e);
	return ans;
}
int main()
{
	double a, b, c;
	double ans, ans1, ans2, ans3, ans4;
	ans = ans1 = ans2 = ans3 = ans4 = (double)(1e12);
	cin >> a >> b >> c >> p1.x >> p1.y >> p2.x >> p2.y;
	ans = fabs(p1.x-p2.x) + fabs(p1.y-p2.y);
	if (fabs(a) < eps){
		q1 = Point(p1.x, -c/b);
		q2 = Point(p2.x, -c/b);
		ans1 = dis(p1,q1) + dis(q1,q2) + dis(q2,p2);
	}
	else if (fabs(b) < eps){
		q1 = Point(-c/a, p1.y);
		q2 = Point(-c/a, p2.y);
		ans2 = fabs(p1.x-q1.x) + dis(q1,q2) + fabs(p2.x-q2.x);
	}
	else {
		q1 = Point(p1.x, -(a*p1.x+c)/b);
		q3 = Point(-(b*p1.y+c)/a, p1.y);
		q2 = Point(p2.x, -(a*p2.x+c)/b);
		q4 = Point(-(b*p2.y+c)/a, p2.y);
		ans1 = dis(p1,q1) + dis(q1,q2) + dis(q2,p2);
		ans2 = dis(p1,q1) + dis(q1,q4) + dis(q4,p2);
		ans3 = dis(p1,q3) + dis(q3,q2) + dis(q2,p2);
		ans4 = dis(p1,q3) + dis(q3,q4) + dis(q4,p2);
	}
	cout << setprecision(10) << fixed << min(ans,ans1,ans2,ans3,ans4) << endl;
}

Problem F - Segments POJ - 3304

难度:★★☆☆☆

定位:思维+计算几何直线与线段相交

题目大意:给出n条线段,问是否能找到一条直线,使所有n条线段在此条直线上的投影至少有一个公共点。

解法:注意开动脑筋,拥有一个公共点,那么就代表着,以这个公共点为垂足,做投影所在直线的垂直线,那么这条垂直线必然和n条线段都相交了。现在题目就转化为了,能不能找到一条直线,与所有线段都相交。枚举任意两个端点,判断二者构成的直线与n条线段是否相交即可完成此题目。

#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
#define eps 1e-8
#define sq(x) ((x)*(x))

const int maxn = 100 + 10;
int n;

struct Point {
	double x, y;
	Point() {}
	Point(double x, double y): x(x), y(y) {}
}	pp[maxn * 2];

struct Seg {
	Point p1, p2;
}	a[110];

Point operator - (Point p, Point q) {
	return Point(p.x-q.x, p.y-q.y);
}

double dist(Point v1, Point v2){
	return sqrt(sq(v1.x-v2.x) + sq(v1.y-v2.y));
}

double cross(const Point & v1, const Point & v2) {
	return v1.x * v2.y - v1.y * v2.x;
}

bool ok(Point p1, Point p2) {
	if(dist(p1, p2) < eps) return false;
	else {
		for(int i = 0; i < n; i++) {
			if (cross(p2 - p1, a[i].p1 - p1)*cross(p2 - p1, a[i].p2 - p1) > eps){
				return false;
			}
		}
	}
	return true;
}

int main() {
	int tt;
	scanf("%d", &tt);
	while (tt --) {
		scanf("%d", &n);
		for(int i = 0; i < n; i++)
			scanf("%lf%lf%lf%lf", &a[i].p1.x, &a[i].p1.y, &a[i].p2.x, &a[i].p2.y);
			int ans = 0;
			if (n == 1) {
				ans = 1;
			}
			else {
				for(int i = 0; i < n; i ++){
					for(int j = i+1; j < n; j ++) {
					if (ok(a[i].p1, a[j].p1) ||
					    ok(a[i].p1, a[j].p2) ||
					    ok(a[i].p2, a[j].p1) ||
					    ok(a[i].p2, a[j].p2)){
						ans = 1;
					}
					if (ans)	break;
				}
			}
		}
		ans ? printf("Yes!\n"):
		      printf("No!\n");
	}
	return 0;
}

Problem G - Keywords Search HDU - 2222

难度:★★★☆☆

定位:AC自动机模板题目

题目大意:给出n个被查询词汇模板串,最后再给出1个查询文本串,问这些被查询的模板串中,有多少个出现在了文本串中。

解法:AC自动机,裸板子。注意,模板串可能会出现重复,所以这里我们需要利用val数组进行标记出现次数,具体方法如下。

坑点:用KMP会超时,且AC自动机的数组大小不能开的过大,否则会MLE。建议学习AC自动机之前,去练习Trie树的相关知识,因为AC自动机的串存储结构是以Trie树为基础的。

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int maxn = 500000 + 10;
struct node{
	int child[maxn][26 + 10];
	int fail[maxn];
	int val[maxn];
	int size;
	node(){
		memset(child, 0, sizeof(child));
		memset(fail, 0, sizeof(fail));
		memset(val, 0, sizeof(val));
		size = 1;
	}
	void init(){
		memset(child, 0, sizeof(child));
		memset(fail, 0, sizeof(fail));
		memset(val, 0, sizeof(val));
		size = 1;
	}
	inline int idx(char c){
		return c - 'a';
	}
	void insert(string s){
		int n = s.size();
		int u = 0;
		for (int i = 0; i < n; i ++){
			int c = idx(s[i]);
			if (!child[u][c]){
                                memset(child[size], 0, sizeof(child[size]));
                                val[size] = 0;
                                child[u][c] = size ++;
			}
			u = child[u][c];
		}
		val[u] ++;
	}

	int find(string t){
		int ans = 0;
		int n = t.size();

		int j = 0;
		for (int i = 0; i < n; i ++){
			int c = idx(t[i]);
			while (j && !child[j][c])	j = fail[j];
			j = child[j][c];
			int jj = j;
			while (val[jj]){
				ans += val[jj];
				val[jj] = 0;
				jj = fail[jj];
			}
		}
		return ans;
	}

	void getFail(){
		fail[0] = 0;
		queue<int> qq;

		for (int i = 0; i < 26; i ++){
			if (!child[0][i])	continue;
			else {
                                fail[child[0][i]] = 0;
                                qq.push(child[0][i]);
			}
		}

		while (!qq.empty()){
			int u = qq.front();
			qq.pop();
			for (int i = 0; i < 26; i ++){
				if (!child[u][i])	continue;
				else {
					qq.push(child[u][i]);
					int v = child[u][i];
					int j = fail[u];
					while (j && !child[j][i])	j = fail[j];
					fail[v] = child[j][i];
				}
			}
		}
	}

}	ACM;

int main()
{
	ios::sync_with_stdio(false);
	int tt;
	cin >> tt;
	int n;
	while (tt --){
                ACM.init();
                string s, t;

                cin >> n;
                for (int i = 0; i < n; i ++){
			cin >> s;
			ACM.insert(s);
                }
                ACM.getFail();

                cin >> t;
		cout << ACM.find(t) << endl;
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sycamore_Ma

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值