比赛:
http://oj.hzjingma.com/contest/view?id=71
思路一、枚举
根据题目意思,。由于。所以我们可以枚举所有的 x,判断是否满足条件
根据题目的 p 的数据范围,。不会超时。如果有成立的 x 存在,说明有一个解,那就答案 + 1。
思路二、数学分析
也可以根据数学分析,我们对于,可以得到 ,根据,可以得到,要么 使得左边为 0,那么就是 p 的倍数。要么 ,使得等于 p 的倍数。
因此,解都是 2 个。
特别的,当 p = 2的时候,是重根,那解为 1 个。
#include
using namespace std;
int main()
{
int p;
cin >> p;
int ans = 0;
for(int x = 1; x < p; ++x)
{
if(x * x % p == 1) ++ans;
}
cout << ans << endl;
return 0;
}
#include
using namespace std;
int main()
{
int p;
cin >> p;
if(p == 2) cout << 1 << endl;
else cout << 2 << endl;
return 0;
}
简单思维题,无论一个数,是质数还是合数,它的倍数,几乎都是合数,除了特别的 1,要至少是 8 和 9 才会是连续的合数。
所以我们知道了差,要找到对应的两个合数,使得这两个合数的差,是输入的值。那么根据 k * x - (k - 1) * x = x,我们可以知道,只要是输入值的连续倍数(满足要都是合数),就可以了。
正常的 >= 2 的,我们可以 2 倍 和 3 倍即可。
但是对应 1 而言, 2 倍 和 3 倍 不行,至少要为 8 和 9 才满足条件。
因此我们特判,如果是 1,那就输出 8 和 9。否则,输出 输入值的 2 倍数和 3倍数。
#include
using namespace std;
int main() {
int n;
cin >> n;
int a, b;
if(n == 1)
{
a = 9;
b = 10;
}
else
{
a = 2 * n;
b = 3 * n;
}
printf("%d %d\n", a, b);
return 0;
}
一开始的想法,枚举少的那一个数,然后求出剩余数的乘积,和。如果乘积都是最大值,那么取出,和中的最大值。
这个方法的时间复杂度应该是 O(N ^ 2),因为枚举缺少的数,剩下的数还要遍历一次相乘,相加。
根据 N 的范围不会超时。但是根据每一个值的大小,那么可能的乘积大小,最大为 10 ^ (2 * N),这个超出了数据范围,无法存储(除非用大数)。
那么能不能根据分类讨论呢,因为是要从 N 个数中,选出 N - 1 个数。我们可以根据 数据中 0 的个数分类讨论
#include
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
int main() {
int n, a;
cin >> n;
vector nums;
int zeros = 0;
int sum = 0;
int fushu = 0;
int zhengshu = 0;
for(int i = 0;i < n; ++i) {
cin >> a;
nums.push_back(a);
sum += a;
if(a < 0) ++fushu;
if(a > 0) ++zhengshu;
if(a == 0) ++zeros;
}
sort(nums.begin(), nums.end());
if(zeros >= 2)
{
sum -= nums[0];
cout << sum << endl;
return 0;
}
if(zeros == 1)
{
if(fushu % 2 == 0)
{
cout << sum << endl;
}
else
{
cout << sum - nums[0] << endl;
}
return 0;
}
if(fushu % 2 == 0) // 正数
{
if(zhengshu == n){
sum -= nums[0];
}
else if(fushu == n) {
sum -= nums[0];
}
else
{
int tep = INF;
for(int i = 0;i < n; ++i){
if(nums[i] < 0) continue;
tep = min(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
else // 结果是负数
{
if(fushu == n) {
sum -= nums[n - 1];
}
else
{
int tep = -INF;
for(int i = 0;i < n; ++i){
if(nums[i] > 0) continue;
tep = max(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
return 0;
}
要求是三角形,那就是,任意两边之和,大于第三边。
一开始的想法,是枚举所有可能的三个值,那么复杂度是 O(N ^ 3)。根据数据范围,会超时。
因此,我们要降低时间复杂度。根据数据范围,时间复杂度最大不能超过 O(N ^ 2)。
那么也就是要两层遍历,就要得到答案。
我们如果第一层循环,先固定三条边中的最大值,那么根据两边之和大于第三边,剩下的条件,就是剩下的两条没确定的边之和要大于这个 三条边的最大值。
我们对数组先排序,然后第二层循环的时候,我们 left 是剩下数的第一个值,right 是剩下数的第二个值,类似二分查找。
这样子,第二层循环,我们用了双指针,双指针刚好一起走的一次数组。
所以时间复杂度是 O(N ^ 2)
这道题也是一道LeetCode的题目,第 611。
#include
using namespace std;
#define LL long long
const int MAXN = 1e4 + 10;
LL nums[MAXN];
int main() {
int n;
scanf("%d", &n);
for(int i = 0;i < n; ++i) {
scanf("%lld", &nums[i]);
}
LL count = 0;
sort(nums, nums + n);
for (int i = n - 1; i >= 2; i--) {
int left = 0, right = i - 1;
while(left < right) {
if (nums[left] + nums[right] > nums[i]) {
count += (right - left) * 1ll;
right--;
}
else {
left++;
}
}
}
cout << count << endl;
return 0;
}
对于少部分的数据,可以解决的。对于大范围的数据,是要分块打表。具体可以看官方题解,不是很懂
http://oj.hzjingma.com/contest/editorial?id=71
代码就去看官方比赛AC的人的代码
数学公式题,根据题意,是求的前 n 项和,根据等差数列前 n 项和 公式,还是平方数的前 n 和公式,我们可以得到
注意是要求 MOD,同时,我们注意 n 的取值很大,所以对于 n 我们就需要求 MOD。
还有一个地方要注意,对于除法的取模,需要计算逆元。
也可以不用计算逆元,不过这个时候,就需要先利用 n 把 3 给约去。
#include
using namespace std;
#define LL long long
const LL MOD = 1e9 + 7;
int main() {
LL res = 0;
LL n;
cin >> n;
if(n % 3 == 0)
{
res = (((((2 * n / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
else if(n % 3 == 1)
{
res = (((((2 * (2 * n + 1) / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((n) % MOD)) % MOD;
}
else if(n % 3 == 2)
{
res = (((((2 * (n + 1) / 3) % MOD) * ((n) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
LL tep = (n % MOD) * ((n + 1) % MOD) % MOD;
res = (res - tep + MOD) % MOD;
cout << res << endl;
return 0;
}
这是一道,DP问题,主要是设好变量
#include
using namespace std;
#define Mod 1000000007
long long a[42];
long long dp[42][42][3][1602];
int main(){
int n;
memset(dp,0,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
if(a[1]==-1){ // 初始条件
for(int i=0;i<=40;i++)
dp[1][i][1][i]=1;
}
else
dp[1][a[1]][1][a[1]]=1;
// 从 2 开始
for(int i=2;i<=n;i++)
{
if(a[i]==-1) // == -1
{
for(int j=0;j<=40;j++) // 当前值 j 都有可能
{
for(int L=0;L<=40;L++) // 上一个值的取值 L 都可以转移过来
{
// 对于前 i - 1的和 k,要求 k/(i - 1) >= j,所以 k 从 (i - 1) * j 开始,但是总和值不会超过 1600(因为最长 40,每一个元素最大 40).所以 k + j <= 1600确定上界
for(int k=j*(i-1);k<=1600-j;k++)
{
if(j>=L)
{
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][1][k])%Mod;
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][j][2][k+j]=(dp[i][j][2][k+j]+dp[i-1][L][1][k])%Mod;
}
}
}
}
else // a[i] != -1,也就是上面 j = a[i],是确定的
{
for(int L=0;L<=40;L++)
{
for(int k=a[i]*(i-1);k<=1600-a[i];k++)
{
if(a[i]>=L)
{
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][1][k])%Mod;
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][a[i]][2][k+a[i]]=(dp[i][a[i]][2][k+a[i]]+dp[i-1][L][1][k])%Mod;
}
}
}
}
// 最后答案累加,对于 n 结束的时候,枚举 num,k,sum的所有可能成立的序列
long long sum=0;
for(int j=0;j<=40;j++){
for(int k=j*n;k<=1600;k++){
sum=(sum+dp[n][j][1][k])%Mod;
sum=(sum+dp[n][j][2][k])%Mod;
}
}
cout<
注意到,此题如果真的去一一枚举dist,那么复杂度最优也是O(n^2logn)的
我们考虑dist的意义。对于每一条边,这条边两端上,左半边的点与右半边的点,一定会经过这条边。所以这条边会被经过左半边点数 * 右半边点数,这条边的权值是w,所以这条边产生的答案贡献就是次数 * 权值。
把所有边的贡献加起来就是答案了。
因此,先要构建图,然后 DFS,我们从一个节点 u,找到 v 节点 的时候,需要统计(u --- v), v 那边的的点数,因为我们需要 DFS 还可以附带计算 某一个点这边的点数,我们用 一个数组 sz 记录。
从 u 父节点开始的时候,表示在 u 这边有一个点(u 本身),所以 sz[u] = 1。
然乎遍历这个 u 的所有子节点 v,得到 v 的时候,由于我们要先计算 v 这边都有多少点(从而得到 一个半边的点数,同时另一半边的点数 是 n - 另一边,从而计算答案)。所以我们先继续 DFS,等这里的 DFS 返回的时候,表示 sz[v] 这里计算好了,那么就计算。同时这里返回得到 v 的点数,那么我们要更新 u 的点数(因为 u 的这边,包括了 v,所以 v 的点数,加回到 u 上),sz[ u ] += sz[ v ]。
#include
using namespace std;
#define ll long long
const int N = 200000 + 11;
typedef pair pii;
ll ans;
int n;
vector G[N]; // 图
int sz[N];
void DFS(int u,int f){
sz[u] = 1; // 这个点的点数 初始化,就自己 = 1
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].first;
if(v == f) continue; // 因为是 u 到 v,如果 u 回到了自己的父节点,那就不考虑,因为是要找子节点
DFS(v, u); // 先继续 DFS,因为 DFS的一个隐返回值,可以返回,这个点半边的点数
ans = (ans + sz[v] * 1ll * (n - sz[v]) * G[u][i].second);
sz[u] += sz[v]; // 返回了 v 的点数,那么 u 的点数,累加 v 起来
}
}
int main(){
scanf("%d", &n);
for(int i = 0;i < n - 1; ++i)
{
int a, b, c; scanf("%d%d%d", &a, &b, &c);
G[a].push_back(pii(b, c));
G[b].push_back(pii(a, c));
}
ans = 0; // 答案
DFS(1, -1); // 随便从某一个节点出发
printf("%lld\n", ans);
return 0;
}
KMP的题目(等待学习中)
看到这个Alan Walker值的定义,如果对kmp熟悉的选手一定知道,这就是next[]数组的含义。
所以跑一遍kmp,然后取个min就好了。
#include
using namespace std;
int main() {
ios :: sync_with_stdio(false); cin.tie(0);
string s; cin >> s;
int n = s.size(); s = ' ' + s;
vector kmp(n + 1);
for (int i = 2, j = 0; i <= n; i++) {
while (j > 0 && s[i] != s[j + 1]) {
j = kmp[j];
}
if (s[i] == s[j + 1]) {
j++;
}
kmp[i] = j;
//cout << "kmp[" << i << "] = " << kmp[i] << '\n';
}
int q; cin >> q;
while (q--) {
int l, k;
cin >> l >> k;
if (kmp[l] >= k) {
cout << 0 << '\n';
} else {
cout << k - kmp[l] << '\n';
}
}
return 0;
}