做题随笔1
前缀后缀的预处理
如接雨水
对于某一个柱子 \(i\),它左右两边柱子的较小值决定这个柱子能盛多少水,预处理前缀后缀的最小值即可
自然数前缀异或与二次前缀异或
对于自然数 \(1-n\) 的前缀异或,规律如下
对于自然数 \(1-n\) 前缀异或的前缀异或(即二次前缀异或),规律如下
\(r=x\) \(mod\) \(8\)
二分边界的选取
https://codeforces.com/problemset/problem/1538/C
可以把 \(l<=a_i+a_j<=r\) 转化为 \(l-a_j<=a_i<=r-a_j\),对 \(a\) 数组进行排序,然后遍历 \(a\) 数组,对于每一个 \(a_i\) 来说,计算他下一个到最后一个的合法对数,通过 \(lowerbound\) 和 \(upperbound\)实现
计算x进制下式子末尾0的个数
通俗的,先对十进制进行分析,只需要计算式子中每一项含有 \(2\) 和 \(5\) 的个数就可以了,同样的,对x进行质因数分解,只需要计算式子中每一项含各个质因数的最少个数就可以了。
注意1:\(x=p_1^{c1}\cdotp_2^{c2}\cdots p_m^{cm}\) ,找出式子中对于某个质因数方的最小倍数,即为答案
注意2:对于 \(n!\) 数字太大难以计算,通过勒让定理计算 \(n!\) 中质因数 \(p\) 的个数 \(V\),
\(V_p(n!) \equiv \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \left\lfloor \frac{n}{p^3} \right\rfloor + \cdots\)
我们在代码实现不需要计算 \(p^2\) 等等质因数,累加 \(n\) 除以 \(p\) 就可以了
注意3:如果 \(x\) 最后不为 \(1\),那么说明经计算后的 \(x\) 也是一个质因数
Code
点击查看代码
void solve()
{
cin >> n >> m;
int ans = -1;
for (int i = 2; i * i <= m; i++)
{
if (m % i == 0)
{
int cnt = 0;
while (m % i == 0)
{
cnt++;
m /= i;
}
int c2 = 0;
int t = n;
while (t > 0)
{
c2 += t / i;
t /= i;
}
int res = c2 / cnt;
if (ans == -1)
{
ans = res;
}
else
ans = min(ans, res);
}
}
if (m > 1)
{
int t = n;
int c2 = 0;
while (t > 0)
{
c2 += t / m;
t /= m;
}
int res = c2 / 1;
if (ans == -1)
{
ans = res;
}
else
ans = min(ans, res);
}
if (ans == -1)
{
cout << 0 << endl;
}
else
{
cout << ans << endl;
}
}
贡献法
https://codeforces.com/problemset/problem/1648/A
很容易发现,横坐标和纵坐标的计算互不干扰,所以可以分别计算,分别按升序排序后,就可以去除绝对值,对于一个坐标点\((x_i,y_i)\) 来说,比他小的坐标和他组成的组合对答案的贡献是正的 \((i-1)*x_i\),比他大的坐标和他组成的组合对答案的贡献是负的 \((n-i)*x_i\),通过这种方法可以分别计算每一个点横纵坐标对于答案的贡献
Code
点击查看代码
void solve()
{
cin >> n >> m;
vvi a(n + 1, vi(m + 1, 0));
map<int, vi> p, q;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
p[a[i][j]].pb(i);
q[a[i][j]].pb(j);
}
}
int ans = 0;
for (auto [i, c] : p)
{
sort(all(c));
for (int j = 0; j < c.size(); j++)
{
ans += j * c[j] - (c.size() - j - 1) * c[j];
}
}
for (auto [i, c] : q)
{
sort(all(c));
for (int j = 0; j < c.size(); j++)
{
ans += j * c[j] - (c.size() - j - 1) * c[j];
}
}
cout << ans << endl;
}
字符串的处理技巧
思路
对于这种相邻字符不同的01串,一个很经典的思想,就是这种串只有两种形式 \(01010101……\) 或者 \(10101010…………\), 所以,只需要求得原字符串转换到他们的较小值就行了 ,发现枚举是 \(O(n^3)\) 的,我们采取前缀和进行优化,提前算出 \(pre[i]\) 来代表前 \(i\) 个字符与标准串的不同个数,优化到 \(O(n^2)\) 。
Code
点击查看代码
void solve()
{
cin>>s;
n=s.size();
string s1="",s2="";
vi p1(n+5),p2(n+5);
for(int i=0;i<n;i++)
{
if(i&1)
{
s1+='1';
s2+='0';
}
else {
s1+='0';
s2+='1';
}
p1[i+1]=p1[i]+(s1[i]!=s[i]);
p2[i+1]=p2[i]+(s2[i]!=s[i]);
}
int ans=0;
for(int len=2;len<=n;len++)
{
for(int l=0;l+len-1<n;l++)
{
int c1=0,c2=0;
r=len+l-1;
c1=p1[r+1]-p1[l];
c2=p2[r+1]-p2[l];
ans+=min(c1,c2);
}
}
cout<<ans<<endl;
}
借助因子解决问题
https://codeforces.com/problemset/problem/1850/F
思路:对于每一个 大于 \(1\) 小于 \(n\) 的跳跃距离,都是有效的。遍历每一个点,处理每一个点的因子,如果一个跳跃距离是这个点的因子,那么就可以到达这个点,因为 如果 \(x%a_i==0\),那么 \(x%(x/a_i)==0\),所以只需要遍历到 \(\sqrt n\) 即可,时间复杂度是 \(O(n\sqrt n)\) 的
经典dfs搜索
https://atcoder.jp/contests/abc448/tasks/abc448_d
思路:从根开始 \(dfs\) ,用 \(set\) 维护经过的值,及时回溯
Code
点击查看代码
vi adj[M];
vi ans;
int cnt = 0;
set<int> p;
vi a;
void dfs(int cur, int fa)
{
int now = 0;
if (p.find(a[cur]) != p.end())
{
now++;
}
else
{
p.insert(a[cur]);
}
cnt += now;
if (cnt > 0)
{
ans[cur] = 1;
}
for (int i : adj[cur])
{
if (i == fa)
continue;
dfs(i, cur);
}
cnt -= now;
if (now == 0)
p.erase(a[cur]);
}
void solve()
{
cin >> n;
a.resize(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
ans.resize(n + 1, 0);
dfs(1, -1);
for (int i = 1; i <= n; i++)
{
if (ans[i] > 0)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}

浙公网安备 33010602011771号