第十七届四川省赛vp记录
solved 5 problems
\(I\)、\(F\) : @xmjlove
\(H\)、\(J\)、\(K\) :@dbywsc
I.本质不同后缀
思路
使用字典树维护。将所有字符串倒过来,录入前缀树,树的大小即为本质不同后缀的个数。
public static void main(String[] args) throws Exception{
int n=sc.nextInt();
tire=new int[300010][30];
for(int i=1;i<=n;i++){
String s=sc.next();
add(s);
}
System.out.println(size);
}
public static int[][] tire;
public static int size=0;
public static void add(String s){
int node=0;
for(int i=s.length()-1;i>=0;i--){
int id=s.charAt(i)-'a';
if(tire[node][id]==0)tire[node][id]=++size;
node=tire[node][id];
}
}
H. 胡图图
思路
由于最多可以走 \((x \pm 2, y \pm 2)\) 的距离,因此我们考虑尽可能的一次走两步。
将二维方向拆开考虑,计算横坐标移动的距离 \(dx\) 和 纵坐标移动的距离 \(dy\) 。
接下来分情况讨论:
如果 \(dx = dy\) ,那么每次都走 \((\pm 2, \pm 2)\) 就好了,如果是奇数,最后还要补一个 \((\pm 1, \pm 1)\) ,因此总次数为 \(\lceil \frac{2}{dx} \rceil\) 。
否则,当出现一维是 \(0\) ,另一维小于等于 \(2\) 时,无论如何都要花两次才能走到。
否则,对于更大的那一维,每次尽可能的走 \(\pm 2\) ,另一维可以通过每次走 \(\pm 1\) 或者 \(0\) 缩小两维之间的差距。因此答案为 \(\lceil \frac{2}{\max(dx, dy)} \rceil\) 。
代码
void solve(void) {
i64 x, y, X, Y;
std::cin >> x >> y >> X >> Y;
i64 dx = std::llabs(x - X), dy = std::llabs(y - Y);
if(dx == 0 && dy == 0) std::cout << 0 << endl;
else {
if(dx == dy) std::cout << (dx + 1) / 2 << endl;
else {
i64 m = std::max(dx, dy), n = std::min(dx, dy);
if(n == 0 && m <= 2) std::cout << 2 << endl;
else {
std::cout << (m + 1) / 2 << endl;
}
}
}
}
J.四川省赛
思路
显然,直接使用 \(DFS\) 搜五个点的方案会超时。
考虑从中间的 `C` 出发,这样每次只需要找左右两边的 `SC` 和 `PC` 的方案数就行了,分别记作 \(pre_i\) 和 \(suf_i\),根据乘法原理,贡献应该为 \(pre_i \times suf_i\) 。
代码
std::vector<int> G[N];
void solve(void) {
int n; std::cin >> n;
for(int i = 0; i <= n; i++) G[i].clear();
std::vector<char> w(n + 1);
for(int i = 1; i <= n; i++) {
std::cin >> w[i];
}
for(int i = 1; i < n; i++) {
int u, v; std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
i64 ans = 0;
std::vector<i64> cntS(n + 1), cntC(n + 1);
for(int i = 1; i <= n; i++) cntS[i] = cntC[i] = 0;
for(int u = 1; u <= n; u++) {
for(auto v : G[u]) {
if(w[v] == 'S') cntS[u]++;
if(w[v] == 'C') cntC[u]++;
}
}
std::vector<i64> pre(n + 1), suf(n + 1);
for(int i = 1; i <= n; i++) pre[i] = suf[i] = 0;
for(int v = 1; v <= n; v++) {
if(w[v] != 'C') continue;
i64 cnt = cntS[v];
if(cnt == 0) continue;
for(auto x : G[v]) {
if(w[x] == 'C') pre[x] += cnt;
}
}
for (int u = 1; u <= n; u++) {
if(w[u] != 'P') continue;
int c = cntC[u];
if(c < 2) continue;
for(int x : G[u]) {
if(w[x] == 'C') suf[x] += (c - 1);
}
}
for(int i = 1; i <= n; i++) {
if(w[i] == 'C') ans += pre[i] * suf[i];
}
std::cout << ans << endl;
}
F.逆序对
思路
如果当前位置为 \(0\) ,答案会加上当前位置左边 \(1\) 的数量;如果为 \(1\) ,会为所有后面的 \(0\) 做贡献。
\(l\) 数组记录每个位置左边 \(1\) 的数量,\(r\) 数组记录每个位置右边 \(0\)和 ? 的数量
如果当前位置为 \(i\) ,且 \(s_i = ?\) ,那么比较 \(l_i\) 和 \(r_i\) 的大小,如果能为右面做的贡献不如直接加上左边 \(1\) 数量后的贡献,当前位置设置为 \(1\) ,反之为 \(0\) 。
\(r\) 数组记录 ? 的数量,即假定他为 \(0\) ,如果他设置为 \(1\),会至多减少 \(l_i\) 的贡献,但加上 \(r_i\) 的贡献,所以还是最优的
代码
public static void main(String[] args) throws Exception{
int t=sc.nextInt();
int[] l=new int[1000010];
int[] r=new int[1000010];
int[] cnt=new int[1000010];
while (t-->0){
int n=sc.nextInt();
StringBuilder s=new StringBuilder();
s.append(sc.next());
r[n-1]=0;
for(int i=1;i<n;i++){
l[i]=l[i-1];
if(s.charAt(i-1)=='1')l[i]++;
}
for(int i=n-2;i>=0;i--){
r[i]=r[i+1];
if(s.charAt(i+1)=='0'||s.charAt(i+1)=='?')r[i]++;
}
long ans=0;
for(int i=0;i<n;i++){
cnt[i+1]=cnt[i];
l[i]+=cnt[i];
if(s.charAt(i)=='?'){
if(l[i]>r[i])s.setCharAt(i,'0');
else {
s.setCharAt(i,'1');
cnt[i+1]++;
}
}
}
for(int i=0;i<n;i++){
if(s.charAt(i)=='0')ans+=l[i];
}
pw.println(ans);
}
pw.flush();pw.close();
}
K.点分治
思路
正着去依次删除点后分裂非常难写。但是可以倒着来做,每次相当于是将后面的点加入到图中,此时它会和图中的连通块合并,并且成为这棵有根树的根。因此可以用并查集来维护合并的过程。
代码
std::vector<int> G[N];
void solve(void) {
int n; std::cin >> n;
std::vector<int> p(n);
for(int i = 0; i < n; i++) std::cin >> p[i];
for(int i = 0; i <= n; i++) G[i].clear();
for(int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
std::vector<int> P(n + 1);
std::vector<bool> vis(n + 1);
std::vector<int> fa(n + 1);
for(int i = 1; i <= n; i++) P[i] = i;
auto find = [&](auto && self, int x) -> int {
if(P[x] == x) return x;
return P[x] = self(self, P[x]);
};
for(int i = n - 1; i >= 0; i--) {
int u = p[i];
vis[u] = 1;
for(auto v : G[u]) {
if(!vis[v]) continue;
int pu = find(find, u);
int pv = find(find, v);
if(pu != pv) {
fa[pv] = u;
P[pv] = pu;
}
}
}
for(int i = 1; i <= n; i++) {
std::cout << fa[i] << " ";
} std::cout << endl;
}

浙公网安备 33010602011771号