AT做题记录
ABC248E K-colinear Line (*1292)
Title
给出 \(n\) 个点,问有多少条直线经过至少 \(k\) 个点。
\(1\le n\le 300\)
Solution
根据小学学的知识,两点确定一条直线,所以可以暴力枚举两点,再将剩下的点全部扫一遍,看有没有满足条件,时间复杂度为 \(O(n^3)\) ,可以通过。
但这题的难点在于如何判断多个点在不在一条直线上,其实一条直线 \(y=kx+b\) 只要知道 \(k,b\) 就可以判断出点是否在直线上。
假设暴力枚举的两个点为 \((x_0,y_0)\) 和 \((x_1,y_1)\),容易得到
然后再带入其他点计算,但很明显 \(k\) 有精度误差。
我们将带入计算点 \((x,y)\) 的式子写出来:
两边同乘以 \((y_0-y_1)\) 得
因此避免了精度误差,然后还有一点是去重,可以开一个 map<pair<int,int>,bool> 代表两个点所连直线是否计算过,一旦计算过就退出。
最后,还要特判一下 \(x\) 值均相同的情况(例如 \(x=1\) 等直线)。
ABC247E Max Min(*1256)
Title
给你一个长度为 \(n\) 的数列,问有多少二元组 \((L,R)\) 满足:
Solution
据说有一些线段树,单调栈,二分之类的牛逼做法。
事实上本题只要暴力枚举即可。
枚举右端点,记下最后一个等于 \(x\) 和 \(y\) 的位置 \(a,b\),然后再记录下最后一个不满足条件的位置 \(c\) ,注意更新 \(c\) 时 \(a,b\) 要清空。
如果 \(a,b\) 没有清空,这对答案的贡献为 \(\min(a,b)-c\) ,即左端点 \(L\in[c+1,\min(a,b)]\) ,右端点 \(R=i\) 的区间都是合法的。
将所有答案加起来就可以了,时间复杂度为 \(O(n)\)。
ABC246E Bishop 2(*1476)
Title
在 \(n* n\) 的棋盘上有些点能走,有些不能走,问从 \((x_1,y_1)\) 到 \((x_2,y_2)\) 按国际象棋的教主要多少步。
教主:可以朝左上,左下,右上,右下走无数步,但不能跨越棋子。
Solution
考虑BFS,第一次搜索成功即为答案。
但会超时,有以下优化:
- 如果当前点不可以走,那这个方向的其他点也同样不可以走,直接退出
- 如果到当前点的最优次数小于等于当前次数,直接退出,因为可以看成最优方案在加一次转弯
- 越界直接退出
很明显,每个点只会经过一次,所以时间复杂度为 \(O(n^2)\)。
Code
void bfs(int x, int y)
{
memset(vis,63,sizeof vis);
int mx=vis[1][1];
queue<pair<int, int> >q;
queue<int>q1;
q.push(mp(x,y));
q1.push(0);
vis[x][y]=1;
while(q.size())
{
int X=q.front().ft;
int Y=q.front().sd;
int w=q1.front();
q.pop(),q1.pop();
for(int i = 1; i <= 4; i++)
{
for(int j=1;;j++)
{
int nx = X + dx[i]*j;
int ny = Y + dy[i]*j;
if(nx < 1 || nx > n || ny < 1 || ny >n||p[nx][ny]==1||vis[nx][ny]<=w+1)
break;
if(vis[nx][ny]!=mx)continue;
if(nx==tx&&ny==ty)
{
printf("%d",w+1);
return;
}
vis[nx][ny] = min(vis[nx][ny],vis[X][Y]+1);
q.push(make_pair(nx, ny));
q1.push(w+1);
}
}
}
printf("%d",-1);
return;
}
ABC242D ABC Transform(*1286)
Title
给出 \(S_0\),只包含 ABC 三个字母, \(S_i\) 是由 \(S_{i-1}\) 经过以下变换形成的:
- 将字符
A替换成BC,将字符B替换成CA,将字符C替换成AB
多测,问 \(S_t\) 的第 \(k\) 个字符。
Solution
注意到 \(t\) 很大,但 \(k\) 相对 \(|S_t|\) 又很小,所以可以从 \(k\) 入手。
容易发现 \(S_i\) 的第 \(j\) 个字符是由 \(S_{i-1}\) 的第 \(\lceil\frac k 2\rceil\) 个字符变成的,所以如果我们知道 \(j\) 的奇偶和 \(S_{i-1}\) 的第 \(\lceil\frac k 2\rceil\) 个字符就可以求出 \(S_i\) 的第 \(j\) 个字符。
因此我们可以找到 \(S_t\) 是由 \(S_w\) 的第 \(1\) 个字符变成的,并且从 \(S_0\) 到 \(S_w\) 的第 \(1\) 个字符可以直接求出,即 (ch[1]+w-'A')%3+'A'。
计算 \(S_w\) 时可以顺带着计算当前 \(j\) 的奇偶,用数组存一下,然后从 \(S_w\) 开始倒推即可:
- 如果当前 \(j\) 为奇数,
A->B B->C C->A。 - 如果当前 \(j\) 为偶数,
A->C B->A C->B。
当然还要处理亿点点细节,如果直到 \(S_0\) 还没有将 \(j\) 变成 \(1\) ,那就不用算了,直接带入 \(ch_j\)。
ABC246D 2-variable Function(*1148)
Title
问在大于等于 \(n\) 中最小的满足以下要求的数 \(x\) 是多少。
- \(x=a^3+a^2b+ab^2+b^3\) (\(a,b\) 为自然数)
Solution
容易发现 \(1\le a,b\le 10^6\),暴力从小到大枚举 \(a\) ,令 \(a'=a+1\),根据函数的增减性可知 \(b\le b'\),因此直接将 \(b\) 不断减小即可,时间复杂度为 \(O(n)\)。
当然枚举 \(a\),二分 \(b\) 也可以,时间复杂度为 \(O(n\log n)\)。
(在时间复杂度中的 \(n\) 为 \(10^6\) 级)
ARC138A Larger Score(*837)
Title
给出长度为 \(N\) 的数列 \(a\),定义 \(s=\sum_{i=1}^k a_i\),初始权值为 \(s_0\),现在可以将数列中的相邻数字交换,使得 \(s>s_0\),问最小交换次数,无解输出 \(-1\)。
\(2 \le N \le 4\times10^5\)
Solution
很明显,需要在 \([k+1,N]\) 中选一个大于前 \(k\) 个数中的某一个进行交换,即使得 \(a[m]>a[i](k+1 \le m \le N,1 \le i \le k)\) 进行交换,即可满足条件,交换次数为 \(m-i\)。
如果暴力去搞,复杂度是 \(O(N^2)\) 的,看看数据范围,不能接受。
我们可以把前 \(k\) 个数升序排列,假设说我们对于当前的 \(i\) 寻找到了 \(m\),因为我们升序排列,所以对于所有 \([k+1,m-1]\) 肯定小于 \(a[i+1]\), 所以我们寻找第 \(i+1\) 时,从 \(m\) 往后找,这样可以保证所有数只被扫一次,所以复杂度 \(O(N\log N)\)。
注意虽然排序,但是算交换次数必须要减原来的位置,所以要开结构体记原来的位置。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+7;
struct node
{
int x,id;
}a[N];
bool cmp(node x,node y)
{return x.x<y.x;}
main()
{
int n,m,ans=INT_MAX,l;
scanf("%lld%lld",&n,&m);l=m+1;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i].x),a[i].id=i;
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++)
{
while(l<n&&a[i].x>=a[l].x)l++;
if(a[i].x<a[l].x)
ans=min(l-a[i].id,ans);
}
if(ans>1e6)ans=-1;
cout<<ans;
return 0;
}
ABC194E Mex Min(*1088)
Title
定义 \(\operatorname{mex}(x_1,x_2,...,x_n)\) 指最小的非负整数 \(k\) 使得对于所有 \(x_i\not=k\)。
给出长度为 \(n\) 的数列 \(a_i\) 和一个数 \(m\),求:
Solution
直接暴力去搞时间复杂度为 \(O(na_i)\) 的会超时,注意到本题只是要求出最小值,并不要关心具体的区间。
注意到 \(a_i\) 在可以承受的大小,所以题目就可以变成判断哪个数是满足要求的第一个数。
可以对所有的数都开一个 vector ,其中存这个数在 \(a\) 数列中从前到后出现的位置,如果对于这个数列中存在一个跨度大于 \(m\) ,那这个数一定是合法的,假设 \(f\) 数组是某个数出现的位置且 \(f_j-f_i>m(i<j)\) ,那区间 \([i+1,i+m]\) 的 \(\operatorname{mex}\) 一定小于等于这个数。
题目要求求最小的,那么可以从 \(0\) 开始扫,这样就保证了第一个成功找到的数一定是最小的 \(\operatorname{mex}\)。
容易发现,所有 vector 的大小加起来正好等于 \(n\) ,故时间复杂度为 \(O(n)\)。
ABC251F Two Spanning Trees(*1703)
Title
给出一个图 \(G\),请你构造两个生成树 \(T_1,T_2\),使得:
-
\(T_1\) 使得任何不在树上的边 \((i,j)\) ,节点 \(i\) 和 \(j\) 一定存在一个节点是另一个的祖先。
-
\(T_2\) 使得任何不在树上的边 \((i,j)\) ,节点 \(i\) 和 \(j\) 不存在一个节点是另一个的祖先。
(\(1\) 号节点为根节点)
Solution
先直接给出结论 : \(T_1\) 将图 DFS,\(T_2\) 将图 BFS。
证明如下:
\(T_1\) :
-
对于不在同一层的节点之间就像一条链,所以一定满足要求。
-
对于在同一层的节点之间如果有边 \((i,j)\),那么 DFS 时一定会先扫到点 \(j\) (因为这个点是 \(i\) 的祖先),但事实上并没有,所以同一层的节点之间没有边,满足要求。
\(T_2\) :
- 对于在同一层的节点之间就像菊花图,所以不可能有一个节点是另一个的祖先。
- 对于不在同一层的节点之间如果有边 \((i,j)\),那么如果 \(i\) 是 \(j\) 的祖先,那么 \(i\) 一定是 \(j\) 与 \(i\) 同层的 \(k\) 的祖先过孩子,但事实上并没有,所以不同一层的节点之间没有边。
ABC251E - Takahashi and Animals(*1227)
Title
有 \(N\) 个动物,第 \(i\) 个饲料可以喂养第 \(i\) 和第 \(i+1\) 个动物,特别地,第 \(N\) 个饲料可以喂养第 \(1\) 和第 \(N\) 个动物。第 \(i\) 个饲料需要 \(A_i\) 元,问喂完所有动物至少需要多少钱。
\(2 \le N \le 3\times 10^5,1 \le A_i \le 1 \times 10^9\)
Solution
考虑 \(dp\),设 \(f[i][0/1]\) 表示第 \(i\) 个饲料选或不选所得到的最小值。
分 \(2\) 种情况讨论:如果第 \(1\) 只动物用第 \(1\) 个饲料喂,那么第 \(N\) 个饲料可以选择喂或不喂,那么答案就为 \(\min(f[n][0],f[n][1])\)。如果第 \(1\) 个动物用第 \(N\) 个饲料喂,那么答案就为 \(f[n][1]\)。
对于每次的转移,如果前面的饲料不选,那么当前饲料就必须选,反之,则取最大值即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+100;
int a[N],f[N][2];
main()
{
int n,ans=1e18;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
memset(f,127,sizeof f);
f[1][1]=a[1];
for(int i=2;i<=n;i++)
{
f[i][1]=min(f[i-1][1],f[i-1][0])+a[i];
f[i][0]=f[i-1][1];
}
ans=min(ans,min(f[n][1],f[n][0]));
memset(f,127,sizeof f);
f[1][0]=0;
for(int i=2;i<=n;i++)
{
f[i][1]=min(f[i-1][1],f[i-1][0])+a[i];
f[i][0]=f[i-1][1];
}
ans=min(ans,f[n][1]);
cout<<ans;
return 0;
}
ABC248D - Index Trio (*983)
Title
给你一个长度为 \(n\) 的数列,问有多少三元组 \((i,j,k)\) 满足:
Solution
这题实际上就是要找到有多少组 \((i,j,k)\) 满足 \(a_i* a_j=a_k\),容易想到枚举每一个 \(a_k\) 的约数 \(j\)。
开一个数组 \(f\) 记录每个 \(a_i\) 出现了多少次,根据乘法原理,对答案的贡献为 f[j] * f[a[i] / j] * 2。
最后,还要特判一下 \(j* j=a_i\) 的情况。这时候,对答案的贡献为 f[j] * f[a[i] / j]。(自行理解)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch = getchar();
int r = 0, w = 1;
while (!isdigit(ch))w = ch == '-' ? -1 : 1, ch = getchar();
while (isdigit(ch))r = (r << 3) + (r << 1) + (ch ^ 48), ch = getchar();
return r * w;
}
const int N = 1e6 + 7;
int a[N], f[N], ans;
main() {
int n = read();
for (int i = 1; i <= n; i++) a[i] = read(), f[a[i]]++;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= a[i]; j++) {
if (a[i] % j == 0) {
if (f[j] != 0 && f[a[i] / j] != 0) {
if (j * j == a[i]) ans += f[j] * f[j];
else
ans += f[j] * f[a[i] / j] * 2;
}
}
}
}
cout << ans;
}
ABC251D - At Most 3 (Contestant ver.)(*1463)
Title
给你一个 \(W\) ,需要你给出一组数列 \(a_1,a_2...a_N\) ,至多选 \(3\) 个数相加,使得 \(1\) 到 \(W\) 的所有整数被表示。
- \(1\le N \le 300\)
- \(1\le a_i \le 10^6\)
- \(1\le W \le 10^6\)
Solution
观察题目范围可得,\(\large 1\le W \le 10^6\)
So我们可以将题目翻译为:用一组长度为 \(300\) 的数列至多选 \(3\) 个数相加,来表示 \(1到10^6\) 的所有整数。那么怎么表示呢,考虑到 \(1到10^6\) 实际上都可以分为三段:\(x , x*100,x*10000\)。 那么初步构造数组为:
\(1,2,3...100\)
\(100,200,300...10000\)
\(10000,20000,30000,1000000\)
再看一眼我们构造数组的长度 ,\(300\),恰好与题目范围相符,所以输出这 \(300\) 个数即可。
Code
#include<bits/stdc++.h>
using namespace std;
main() {
cout<<300<<endl;
for(int i=1;i<=100;i++)cout<<i<<" ";
for(int i=100;i<=10000;i+=100)cout<<i<<" ";
for(int i=10000;i<=1000000;i+=10000)cout<<i<<" ";
}

浙公网安备 33010602011771号