校赛

B 文氏图面积

算两个圆覆盖的总面积。
面积即为中间几何体 + 两边扇形面积
如下pic
扇形面积即为:

\[S_{\text{几何体}} = \frac{1}{2} \sqrt{(-d+r_1+r_2)(d+r_1-r_2)(d-r_1+r_2)(d+r_1+r_2)}. \]

其中根号部分可由海伦公式得到

\[p = \frac{d + r_1 + r_2}{2}, \]

三角形面积(几何体由两个三角形组合而成)

\[S_{\triangle} = \sqrt{p(p-d)(p-r_1)(p-r_2)} = \frac{1}{4}\sqrt{(d+r_1+r_2)(-d+r_1+r_2)(d-r_1+r_2)(d+r_1-r_2)}. \]

因此,

\[\sqrt{(-d+r_1+r_2)(d+r_1-r_2)(d-r_1+r_2)(d+r_1+r_2)} = 4 S_{\triangle}. \]

设两圆圆心距为\(d\),半径分别为 \(r_1\)\(r_2\)(不妨设 \(r_1 \ge r_2\)),且两圆相交(\((|r_1 - r_2| < d < r_1 + r_2\))。则相交部分面积为:

\[S_{\text{双扇形面积}} = r_1^2 (π -\arccos\left(\frac{d^2 + r_1^2 - r_2^2}{2 d r_1}\right)) + r_2^2 (π - \arccos\left(\frac{d^2 + r_2^2 - r_1^2}{2 d r_2}\right)) \]

最终答案即为

\[\begin{aligned} res &= S_{\text{双扇形面积}} + S_{\text{几何体}} \\ &= r_1^2 \left( \pi - \arccos\left(\frac{d^2 + r_1^2 - r_2^2}{2 d r_1}\right) \right) \\ &\quad + r_2^2 \left( \pi - \arccos\left(\frac{d^2 + r_2^2 - r_1^2}{2 d r_2}\right) \right) \\ &\quad + \frac{1}{2} \sqrt{(-d+r_1+r_2)(d+r_1-r_2)(d-r_1+r_2)(d+r_1+r_2)}. \end{aligned} \]

#include <bits/stdc++.h>

using namespace std;
typedef pair<double , double>pii;
struct c//circle
{
	double r;//半径 
	pii pt;//point
}; 
double pi = acos(-1);
void input(c &qwq)
{
	cin >> qwq.pt.first >> qwq.pt.second >> qwq.r; 
}
void solve()
{
	c c1, c2;
	int n;cin >> n;
	if(n == 1)
	{
		input(c1);
		cout << fixed << setprecision(4) << c1.r * c1.r * pi << '\n'; 
	}
	else
	{
		input(c1),input(c2);
		double dis = sqrt((c1.pt.first - c2.pt.first) *  (c1.pt.first - c2.pt.first) + (c1.pt.second - c2.pt.second)*(c1.pt.second - c2.pt.second));
		if(c1.r < c2.r) swap(c1,c2);
		if(dis >= c1.r + c2.r) cout <<  fixed << setprecision(4) << c1.r * c1.r * pi + c2.r * c2.r * pi << '\n';
		else if(dis + c2.r <= c1.r) cout << fixed << setprecision(4) << c1.r * c1.r * pi << '\n';
		else
		{
			//如果是钝角的情况呢,那么在这种情况下我们arcos函数是不是就不适用了?
			//不会出现因为,因为我们计算角度的时候直接用的是dis/r这种算法所以算出来默认是锐角的那一边(注意显然这两个角,要么同时锐角,要么同时钝角) 
            double r1 = c1.r, r2 = c2.r;
            double d = dis;
            double angle1 = acos((d*d + r1*r1 - r2*r2) / (2*d*r1));//dis1/r1 
            double angle2 = acos((d*d + r2*r2 - r1*r1) / (2* d*r2));//dis2/r2
            
            //现在已经知道三条边求面积,直接套一下海伦公式
			//p = (a + b + c) /2,s = sqrt(p * (p - a) *(p - b) *(p - c)); 
            
            double res = (pi - angle1) * r1*r1 + (pi - angle1) *r2*r2 + sqrt((-d+r1+r2)*(d+r1-r2)*(d-r1+r2)*(d+r1+r2))/2;//扇形+ 中间几何形状 
            cout << fixed << setprecision(4) << res << '\n';
		}
	}
}
int main()
{
	int t;cin >> t;
	while(t -- )solve(); 
	return 0;	
} 

C 因子代数0(easy version)

因子代数是指基于全体因子的整数函数。考虑如下因子代数:
\(S(n)\) 为正整数 \(n\) 的所有因子按照从小到大的顺序正负交替的平方代数和,
\(S(6)=1^2−2^2+3^2−6^2=−30\)
输入格式
整数 \(n( 1 \le n \le 1000 )\)
输出格式
输出 \(( S(1) + S(2) + \cdots + S(n) )\) 的值。
思路:暴力算因子然后求和就好了呗

#include <bits/stdc++.h>
#define ll long long
using namespace std;

void solve()
{
	int n;cin >> n;
	ll res = 0;
	for(int i = 1; i <= n ;i ++)
	{
		ll t = 0;
		for(int j = 1 ;j <= n ;j ++)
		{
			if(i % j == 0)
			{
				if(t <= 0) t += j * j;//(这个是纯感觉出来的QWQ,没证明不过对了,当然也可以用cnt来判断正负) 
				else t -= j * j;
			}
		}
		res += t;
	}	
	cout << res << '\n';
} 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	solve();
	return 0;	
} 

D 因子代数(hard version)

和上一题的区别只有n的范围变成了\((1≤n≤2×10^6)\)导致\(n^2\)的算法失效了,得优化
思路:第一反应可能会想到优化因子遍历但是这种方法只能优化到\(O(n\sqrt{n})\),数据范围为2e6,计算次数会到2e9仍然是超时的所以还不够
然后想到可以用筛法来求因子,因为这道题n的大小是2e6筛法遍历是完全足够的,说到筛法就想到了d3的Bob and Alice的game那道题传送门QWQ
这里采用埃氏筛去筛出所有因子(欧拉筛只筛最小质因子,不会筛全,所以这里挑选埃氏筛QWQ)
以下是代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e6 + 10;
ll e[N];
bool np[N];
void elurs()
{
	int n = N - 5;
	for(int i = 1 ;i <= n ;i ++) e[i] = 1; 
	for(int i = 2 ;i <= n;i ++)
	{
		if(!np[i])
		{
			e[i] = 1 - (ll)i * i;
		}
		else e[i] += (e[i] <= 0 ? (ll)i * i : -(ll)i*i);
		for(int j = i * 2; j <= n ; j += i)
		{
			np[j] = true;
			if(e[j] <= 0) e[j] += (ll)i * i;
			else e[j] -= (ll)i * i;
		}
	}
}
void solve()
{
	int n;cin >> n;
	ll res = 0;
	for(int i = 1 ;i <= n ;i ++) res += e[i];
	cout << res << '\n'; 
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	elurs();
	solve();
	return 0;	
} 
//Divisive BattleB的代码附上来一同参考
#include <bits/stdc++.h>

using namespace std;
const int M = 1e6 + 10, N = 2e5 + 10;
int p[M], cnt, minfact[M];
bool np[M];
inline int read()
{
	int x = 0, f = 1, c;while(!isdigit(c = getchar())) if(c == '-') f = -1;
	while(x = x * 10 + (c ^ 48), isdigit(c = getchar()));
	return x *f;
}

void elurs()
{
	for(int i = 2 ;i <= M ;i  ++)
	{
		if(!np[i])
		{
			p[++ cnt] = i;
			minfact[i] = cnt; 
		}
		for(int j = 1; p[j] <= M / i ; j ++)
		{
			np[p[j] * i] = true;
			minfact[p[j] * i] = j; 
			if(i % p[j] == 0)
			{
				minfact[i] = j;
				break;
			}
		}
	}
}
void solve()
{
	int n = read();
	vector<int>a(n);
	for(auto &i : a) i = read();
	vector<int>b = a;
	sort(b.begin(), b.end());
	if(a == b)
	{
		cout << "Bob\n";
		return;
	}
	auto isok = [&](int x) -> bool
	{
		if(x == 1) return false;
		int k = x;
		while(k % p[minfact[x]] == 0) k /= p[minfact[x]];
		for(int i = p[minfact[x]] + 1 ;i <= x / i; i ++)
		{
			if(k % i == 0) return true;
		}
		if(k > 1) return true;
		return false;
	};
	vector<int>c, d;	
	for(int i = 0 ;i < n ;i ++)
	{
		if(a[i] == 1)
		{
			c.push_back(1);
		}
		else c.push_back(p[minfact[a[i]]]);
		if(a[i] == p[minfact[a[i]]] ) continue;
		if(isok(a[i]))
		{
			cout << "Alice\n";
			return;
		}
	}
	d = c;
	sort(d.begin(), d.end());
	if(d == c) 
	{
		cout << "Bob\n";
	}
	else cout << "Alice\n";
	
}

int main()
{
	elurs();
	int t = read();
	while(t --) solve();
	return 0;
}

----- 分界线QWQ----

//优化因子取法,太久没写了也写一些hhh
#include <bits/stdc++.h>
#define ll long long
using namespace std;
void solve()
{
	int n;cin >> n;
	vector<int>res;
	for(int i = 1 ;i <= n/i ; i ++)
	{
		if(n % i == 0)
		{
			res.push_back(i);
			if(i != n/i) res.push_back(n / i);
		}
	}
	for(int i = 0 ;i < res.size() ;i ++) cout << res[i] << ' ';
} 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	solve();
	return 0;	
} 

E 智慧增长之路

题目描述
在一个充满智慧和奇迹的小镇上,住着一个聪明而好奇的小W。小W喜欢面临挑战,通过巧妙的思考和勤奋努力来解决问题。一天,他发现了一个神奇的数字谜题,这个谜题隐藏在一个由n个整数组成的数组a中。
这个数字谜题的规则是,小W可以选择任意相邻的两个数字,只要前一个数字小于等于后一个数字,就可以将前一个数字增加1。而小W最多可以执行k次这样的操作。他的目标是通过这些操作,使得整个数组中的最大值尽可能地大。
小W迫不及待地开始了解这个数字谜题的奥秘。他意识到这是一个关于智慧增长的旅程,每一步都是他通向成功的一个台阶。小W决定运用他的数学智慧,巧妙地设计一系列的操作,使得数组中的每个元素都能够尽量增加。请你帮小W计算出在最优操作下最多执行k次操作后数组中的最大值。
输入格式
第一行包括一个整数T(1≤T≤100)(表示有T组测试样例)。
对于每种测试样例,第一行包括两个整数n (\(2≤n≤1000\))和k (\(1≤k≤10^8\)),分别为数组a的长度和可以执行的最大操作数。
每个测试用例的第二行包含n个整数\(a_1,\cdots,a_n\) (\(1≤ai≤10^5\))——数组a的元素。保证所有测试用例的n之和不超过1000。
输出格式
输出T行,对于每个测试用例,输出一个整数——最优操作下执行最多k次操作后数组的最大可能值。
思路1(贪心模拟):题目给出n是1000,所以大概是要求在\(O(n^2)\)的时间复杂度内完成。
但这里还是进行了一定程度的剪枝:
有些情况根本不用考虑,比如从右往左看(因为这里的操作要求后面大前面小从后往前操作)的时候如果\(a_i\)小于等于\(a_{i - 1}\)那么我们根本不用考虑\(a_i\),因为对于\(a_{i}\)前面的最终调出来的最大值,我们\(a_{i - 1}\)也一定能调出且最终得到的最大值比其大。(这是我们优化的重点,有种单调队列的味道)
而对于前面变大了的情况我们就要去遍历一遍看看了。(注意构造出来的数的形状就是一个阶梯,性价比最高)在遍历的过程中我们记录一下操作次数和当前最大值。
但是注意这里有一个阶梯下沉操,因为此时用完所有操作次数后得到的不一定是我们所要求的最大值,可能后面还有阶梯可以走(就是省下一些操作次数,可能后面的阶梯可以走的更远,答案可能在更远的地方),不过最起始点高的阶梯就是当前这个从tail的下一个开始增长的这个。
这个阶梯式跳跃检查的思路真的很惊艳,收了QWQ。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

void solve() {
    int n, k; cin >> n >> k;
    vector<int> a(n + 1, 0);
    for(int i = 1; i <= n; ++ i) cin >> a[i];
    ll tail = n,maxv = a[tail], nowb = 0, price = 0;
    bool flag = true;//记录第一次进入while循环
    while(flag || price > k) {
        flag = false;
        int b = nowb; price = 0;//b为增量,price为当前的代价
        for(int i = tail - 1; i > 0; -- i) {
            b ++;
            price += a[tail] + b - a[i];
            if(price <= k) maxv = max((ll)a[tail] + b, maxv);
            if(a[i] >= a[tail] + b) tail = i, b = nowb = 0, price = 0;//找到新tail,从最新的tail开始更新,并初始化新状态
        }
        -- nowb;//状态递减,模拟整个阶梯下降一层
    }
    cout << maxv << "\n";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t; cin >> t;
    while(t --) solve();
    return 0;
}

G 小a的卡牌游戏

交互题
题目如下
小 a 最近迷上了卡牌。某天,他在教室里遇到了小 c。
小 c 给小 a 出了一道题:他切出了三堆卡牌,每堆都有n张牌,每张牌有一个点数p(\(1≤p≤1000\)),在一开始,小 c 会告诉你三堆牌顶点数之和。
小 a 不知道任何具体牌面,但是他可以选择一堆并移走其当前最上面一张牌,小 c 会立刻告诉你当前三堆牌顶点数之和。
小 a 的目标是在最多3n次操作下,求出三堆牌顶点数之和可能达到的最大值。

#include <bits/stdc++.h>

using namespace std;

void ask(int type, int &pre, int &maxv)
{
	cout << type<<'\n';
	cout.flush();
	int x;cin >> x;
	pre = x;
	maxv = max(maxv , x);
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, s;cin >> n >> s;
	int pre = s, maxv=  s,t = s;
	for(int i = 1 ;i < n ;i ++) ask(1, pre, maxv);
	s += maxv - t;
	maxv = pre;
	t = pre;
	for(int i = 1 ;i < n ;i ++) ask(2, pre, maxv);
	s += maxv - t;
	maxv = pre;
	t = pre;
	for(int i = 1 ;i < n ;i ++) ask(3, pre, maxv);
	s += maxv - t;
	maxv = pre;
	cout << "! "<< s << '\n';
	cout.flush();
	return 0;
}

I 没有87

输出a~b之间的所有没有87的数字(a,b范围为小于1e18)
思路:常规数位dp,直接搜索即可以下是代码,\(时间复杂度 = 状态数 * 状态计算\),也就是说我们最多只有8800次计算,所以数位dp一般是很快的QWQ
以下是代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll pow10[20];
ll dp[20][11][2][2];
bool vis[20][11][2][2];//代表当前情况已经访问 ,也可以用时间戳优化,就是再多加一维即vis[20][11][2][2][] 
ll dfs(vector<int>&a, int pos, int pre,  bool lim, bool have87)
{
	if(a.size() == pos) return have87;//如果已经走完了,看一下合法情况,如果合法就直接返回
	if(vis[pos][pre][lim][have87]) return dp[pos][pre][lim][have87];
	ll res = 0;
	if(!lim && have87)//一步剪枝,如果当前是非限制状态并且以前前面出现过87了就直接排列组合返回即可
	{//当然你不写也可以。也是可以AC的
		vis[pos][pre][lim][have87] = true;
		return dp[pos][pre][lim][have87] = pow10[a.size() - pos];
	}
	int lea = (lim ? a[pos] : 9);//看一下当前位遍历是否存在限制
	for(int i = 0; i <= lea ; i ++)
	{
		int npre = (pre == 10 && !i ? 10 : i);//看一下下一层的pre是什么,注意这里要多考虑一个前导零状态,用来区分如907和7这种(7在我们眼里是007)
		bool nlim = lim && (i == lea), nhave87 = have87 || (pre == 8 && i == 7); 
		res += dfs(a, pos + 1 , npre, nlim, nhave87);
	}
	vis[pos][pre][lim][have87] = true;
	return dp[pos][pre][lim][have87] = res;//记忆化
}
ll cal(ll n)
{
	//这里没给t的范围用不了时间戳所以只能这样每步初始化了
	memset(dp, 0, sizeof dp);
	memset(vis, 0, sizeof vis);
	ll k = n;
	vector<int>a;
	while(k)
	{
		a.push_back(k % 10);
		k /= 10;
	}
	//分解以后是最低位在 
	reverse(a.begin(),a.end());
	return n - dfs(a, 0, 10, true, false); 
} 
void solve()
{
	ll a, b;cin >> a >> b;
	cout << cal(b) - cal(a - 1) << '\n';//类前缀和
}
int main()
{
	pow10[0] = 1;
	for(int i = 1 ;i < 20 ;i ++) pow10[i] = pow10[i - 1] * 10;
	int t;cin >> t; 
	while(t -- ) solve();
	return 0;
}

J Kirito的最大异或值

思路:01字典树求最大异或对,至于这里要求的找出其中异或的的数要求小于m,我们可以通过离线询问的技巧进行处理(就是存储询问,然后按照m升序排个序,这样子我们就可以根据小于等于m这个条件去构造我们的字典树,之后要做的就是求一下当前字典树的最大异或值罢了)
代码如下

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10, M = N * 31;
class Trie
{
	private:
		vector<array<int, 2>>son;
		int idx;
	public:
		Trie(int n)
		{
			son.resize(n + 1);
			idx = 0;
		}
		void insert(int x)
		{
			int pt = 0;
			for(int i = 30 ;i >= 0 ;i -- )//int 2^31~0 
			{
				int t = (x >> i) & 1;
				if(!son[pt][t]) son[pt][t] = ++ idx;
				pt = son[pt][t];
			}
		}
		ll query(int x)
		{
			ll res = 0, pt = 0;//注意这里直接返回的就是异或值 
			for(int i = 30 ;i >= 0 ;i -- )
			{
				int t = (x >> i) & 1;
				if(son[pt][t^1]) pt = son[pt][t^1], res |= (1LL << i);
				else if(son[pt][t]) pt = son[pt][t];
				else return -1;
			}
			return res;
		}
};
struct qr
{
	int x, m, id;
};
void solve()
{
	int n;cin >> n;
	vector<int>a(n);
	Trie tree((n + 1) * 31);
	for(int i = 0 ;i  < n ;i  ++ ) cin >> a[i]; 
	int q;cin >> q;
	vector<qr>Query(q);
	for(int i = 0; i < q ;i ++) 
	{
		qr qwq;
		cin >> qwq.x >> qwq.m;
		qwq.id = i;
		Query[i] = qwq;
	}
	sort(a.begin(), a.end());
	sort(Query.begin(), Query.end(), [&](qr i, qr j)
	{
		return i.m < j.m;
	});
	vector<ll>res(q);
	int pt = 0;
	for(int i = 0 ;i < q ;i ++)
	{
		while(pt < n && a[pt] <= Query[i].m) tree.insert(a[pt ++ ]);
		res[Query[i].id] = tree.query(Query[i].x);
	}
	for(int i = 0 ;i < q; i ++) cout << res[i] << '\n'; 
}
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	solve();
}
posted @ 2026-03-27 17:13  Mikan_QWQ  阅读(0)  评论(0)    收藏  举报