A、憧憬
思路:
通过两个点 (x1,y1), (x2,y2) 确定一个向量(a,b) = (x1-x2, y1-y2)
。
判断两个向量 (a,b), (c,d) 是否平行:
方法1:若 a*d = b*c
,则两个向量平行。
方法2:向量看作过原点的直线,比较斜率。但是这个斜率不是直接纵横坐标相除,而是用pair存最简分数来表示:
如果两个向量的最简分数相同或者互为相反数(分子互为,分母互为),则斜率相同。 (再次安利 芬兰木棋 这道题!)
bool pd()
{
PII k1,k2;
int g=__gcd(abs(x),abs(y));
k1={
x/g,y/g};
g=__gcd(abs(xx),abs(yy));
k2={
xx/g,yy/g};
if(k1==k2||k2.first==-k1.first&&k2.second==-k1.second) return 1;
return 0;
}
由此可以想到,也可用向量来判定两条线段是否平行。
H、终别
思路:
如果不考虑使用魔法的话,消灭n个怪兽所用的最小操作数是:
采用贪心策略,每次连续三个位置,先将第一位置怪兽血量变为0,再将第二位置变为0…
但是现在可以有两个相邻位置i,j可以直接消灭,也就是不用考虑。
所以将该问题划分为[1,i-1], [j+1,n]
两个子问题。
可以遍历不用消灭的两个相邻位置。
如果每次遍历都一遍的话,复杂度为 O ( n 2 ) O(n^2) O(n2)。
所以需要预处理出pre[i]
:1~i 个怪兽需要的最小操作数,last[i]
:i~n个怪兽需要的最小操作数。
ans = min(ans, pre[i-1] + last[i+2])
。
关键是要认识到两个点:
- 将一个区间全部变为0的操作数是固定的,贪心实现。
- 预处理的很妙!
Code:
const int N = 2000010, mod = 1e9 + 7;
int T, n, m, a[N], b[N];
ll pre[N],last[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
for(int i=1;i<=n;i++)
{
pre[i]=pre[i-1];
if(b[i]>0){
pre[i]+=b[i];
b[i+1]-=b[i];
b[i+2]-=b[i];
}
}
for(int i=1;i<=n;i++) b[i]=a[i];
for(int i=n;i>=1;i--)
{
last[i]=last[i+1];
if(b[i]>0){
last[i]+=b[i];
b[i-1]-=b[i];
b[i-2]-=b[i];
}
}
ll ans=1e18;
for(int i=1;i<=n-1;i++)
{
ans=min(ans,pre[i-1]+last[i+2]);
}
cout<<ans;
return 0;
}
G、冷静
思路:
每次询问给定 n 和 K,问 1 ~ n 中有多少数可以表示为大于等于 K 的质数的乘积。
由算数基本定理:
所以要计算出每个数的最小质因子,提前储存起来。
通常的方法是 埃式筛:
因为 埃式筛 每个数都是被其最小质因子筛掉的,所以复杂度O(n)。所以标记出每个数是由谁筛掉的就行了。
void Prim(int n)
{
for(int i=2;i<=n;i++)
{
if(!f[i]) prim[++cnt]=i,ans[i]=i;
for(int j=1;prim[j]<=n/i;j++)
{
f[prim[j]*i]=1;
ans[prim[j]*i]=prim[j];
if(i%prim[j]==0) break;
}
}
}
但是今天又想到一种方法:分解质因数 。还不确定对不对。
每个数都可以分解为若干个质数的乘积,所以找出第一个分解的质因数便是最小质因数。
void prim(int n)
{
for(int i=2;i<=n/i;i++)
{
if(n%i==0){
f[n]=i; return;
}
}
f[n]=n;
}
之后便是判断一个区间中有多少个数的最小质因子不小于K,用 树状数组 或 线段树。