Logo __vector__ 的博客

博客

标签
暂无

论如何 AK ARC211

A

注意到 $5$ 是一个特殊的数字,因为除了 $5$ 以外任意相同数字都允许相邻。

  • 如果给定的数字存在 $5$。
    假设有 $x$ 个 $5$,那么强制使用 $x-1$ 个其他数字将这些 $5$ 隔开。
    令 $y$ 表示不是 $5$ 的数字数量。
    若 $y \le x-1$,那么显然添加 $x-1-y$ 个非 $5$ 数字填到空里面,就能变得合法。
    否则,先对于总共 $x-1$ 个空,每个空填一个数字,剩下的没填的数字,填到已经填了的且填的数值和自己相同的空里面即可。
    综上,答案为 $\max(0,x-1-y)$。
  • 如果给定的数字不存在 $5$。
    可以证明除了仅存在两种数字,且这两种数字加起来总和为 $10$ 的情况的答案是 $1$,其余情况答案均为 $0$。
    考虑将所有相同数字放到一起,视为合并为一个数字。
    然后,升序排列所有数字。
    如果存在相邻两个数字满足加起来总和为 $10$,那么就将其中一个数字与自己另一个邻居数字交换,然后就合法了。
#include <bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
ll a[10];
void solve(int id_of_test){
    FOR(i,1,9)read(a[i]);
    if(a[5]){
        ll rst=-a[5];
        FOR(i,1,9)rst+=a[i];
        if(rst>=a[5]-1){
            puts("0");
        }else{
            printf("%lld\n",a[5]-1-rst);
        }
    }else{
        int cnt=0;
        FOR(i,1,9)cnt+=(a[i]!=0);
        if(cnt==1)puts("0");
        else if(cnt==2){
            FOR(i,1,9){
                FOR(j,i+1,9){
                    if(a[i]&&a[j]){
                        if(i+j==10){
                            puts("1");
                            return;
                        }
                    }
                }
            }
        }else{
            puts("0");
        }
    }
}
int main()
{
    int T;
    read(T);
    FOR(_,1,T){
        solve(_);
    }
    return 0;
}
/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*/

B

观察样例,猜结论:
- 一定可以仅使用 $0,1$ 构造出满足要求的数列。

为了简单的构造答案,考虑让同一种数字构成一个连续段。

首先考虑什么情况下能仅使用 $0$ 构造出答案。

此时,有以下几条限制:
1. 第一个数列长度至少为 $y$。 2. 第二个数列长度至少为 $z$。 3. 第三个数列长度至少为 $z$。

进而推导出:
1. 第一个数列长度等于 $y$。 2. 第二个数列,第三个数列的长度等于 $z$。

可以推导出,当且仅当 $x=y$ 才能与题目条件不冲突。

现在知道 $x=y$ 时可以仅使用 $0$ 构造出答案,其他情况则不可以,考虑其他情况怎么构造答案。

考虑这样:
- 第一个数列先 $x$ 个 $0$,然后 $y$ 个 $1$。

然后可以推导出:
- 第二个数列至少拥有 $x$ 个 $0$。 - 第三个数列至少拥有 $y$ 个 $1$。 - 第二个数列不允许有超过 $x$ 个 $1$。

接下来考虑怎么满足数列二,三的最长公共子串长度为 $z$。

这样构造:

  • 第二个数列拥有 $z$ 个 $0$。
  • 第三个数列加上 $z$ 个 $0$。

然后本题就做完了。

综上:

  • 第一个数列有 $x$ 个 $0$ 和 $y$ 个 $1$。
  • 第二个数列有 $z$ 个 $0$。
  • 第三个数列有 $y$ 个 $1$ 和 $z$ 个 $0$。

C

考虑将所有极长的连续的同一类型的格子分别缩为一个连续段。

首尾如果是树的话就可以忽略掉,因为反正不可能删掉,所以接下来可以假设首尾均为空段。

首先每次操作必然恰好删除一个树段,否则可以通过将本次操作拆分为多次操作来增加贡献,也就是说每次操作必然选中相邻两个空段。

考虑最大奖金的形式是什么。

首先,每个空段的最大值必然会被算进总奖金一次。

然后,每次操作结束之后相当于合并了两个空段和它们中间的那个树段,生成了一个新的空段,最大值相当于三段最大值的 max。

除了最终产生的那个长度为 $n$ 的空段,其余所有中途产生的空段的最大值也都会被算进总奖金一次。

那么答案将会是,每个空段的最大值的总和,加上合并过程中产生的空段的最大值总和,减去 $\max\limits_{i=1}^n r_i$。

注意到只有合并过程中产生的空段的最大值总和不固定。

那么贪心的考虑,能否让每次产生的空段的最大值都等于 $\max\limits_{i=1}^n r_i$ 呢?

这显然是可以的,只需要保证选中的两个空段以及中间的树段的最大值是 $\max\limits_{i=1}^n r_i$ 就行了。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=2e5+5;
char s[maxn];
int cost[maxn];
int n;
vector<pii> rle(){
    vector<pii> seq;
    int l=1,r=n;
    while(s[l]=='#')l++;
    while(s[r]=='#')r--;
    int lst=l;
    FOR(i,l,r){
        if(s[i]!=s[i-1]){
            if(i!=l)seq.eb(mkpr(lst,i-1));
            lst=i;
        }
    }
    if(s[r]=='.')seq.eb(lst,r);
    vector<pii> res;
    for(auto [l,r]:seq){
    //    printf("[%d,%d]\n",l,r);
        int mx=0;
        FOR(i,l,r)ckmx(mx,cost[i]);
        int cnt=0;
        FOR(i,l,r)cnt+=(mx==cost[i]);
        res.eb(mkpr(mx,cnt));
    }
    return res;
}
void solve(int id_of_test){
    scanf("%d",&n);
    scanf("%s",s+1);
    FOR(i,1,n){
        scanf("%d",&cost[i]);
    }
    auto res=rle();
    int mx=0;
    ll ans=0;
    for(auto [_mx,_cnt]:res)ckmx(mx,_mx);
    for(int i=0;i+2<res.size();i+=2){
        if(max({res[i].fi,res[i+1].fi,res[i+2].fi})==mx){
            ans+=1ll*res[i].se*res[i+2].se;
        }
    }
    printf("%lld\n",ans);
}
int main()
{
    int T;
    T=1;
    FOR(_,1,T){
        solve(_);
    }
    return 0;
}
/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*/

D

首先考虑无解情况是什么。

如果存在一个节点 $x$,使得 $1,2$ 无论如何都必须经过一条公共边才能到达 $x$,那么是无解的。

这种情况可以换个描述方式,若存在一个桥,使得断开这个桥之后 $1,2$ 处于同一连通分量,那么无解。

考虑剩下的情况,现在边双缩点之后图将会形成一条链,且 $1,2$ 所在的边双连通分量将会位于链的两端

因为 $1,2$ 的路径上不能挂其他节点,$1,2$ 路径以外也不能挂其他节点,否则无解。

然后就能惊喜的发现,且显然的,只要这样做就是对的:

  1. 从 $1$ 号点出发建立 dfs 树,子节点的蓝色标记指向父节点。
  2. 删除已经用过的边。
  3. 从 $2$ 号点出发建立 dfs 树,子节点的黄色标记指向父节点。

E

主要记录我对官方题解的理解。

考虑树的形态确定的情况下,$R$ 数列将会是怎样的。

首先 $Q_1 = N$。

然后假设 $1$ 的直接连接的子节点有 $x$ 个,那么必然分别填 $N-1,N-2,\cdots,N-x$。

接下来 Takahashi 肯定是递归访问到标记着 $N-x$ 的节点。

随后注意到,这个标记着 $N-x$ 的节点,其子节点的权值一定小于 $N-x$。

所以访问完权值为 $N-x$ 的点之后必然访问其子树(如果存在的话),若没有子树的话,回溯到权值为 $N-x+1$ 的点。

然后使用相同方法给这个权值为 $N-x$ 的点的子节点填权值。

如果循环往复,注意到 Takahashi 的行走过程,访问点的顺序就是 DFS 序,只不过按照点权从小到大依次搜索。

那么此时也就有了一些结论。

对于一个节点 $u$,假设其有 $x$ 个子节点,令第 $i$ 个子树单独提出来得到的 $R$ 数组为 $s_i$,那么这些子节点的权值必然是按照 $s_i$ 字典序降序排列(特别的,若一个是另一个的前缀,则短的在前面)且连续的一组数字。

假设子节点们已经按照上述规则排序,$u$ 节点权值为 $m$,此时,$u$ 节点作为根的得到的 $R$ 数组形式如下:
$$ m,m-x,s_1,m-x+1,s_2,m-x+2,s_3,\cdots,m-1,s_x $$

考虑怎么判定给定的 $P$ 数组能否得到。

首先必须满足 $P_1 = N$。

随后,设计一个检查函数 chk(l,r,mx),表示当前正在处理的子树的权值(不含根)对应 $P_l,P_{l+1},\cdots,P_r$,且根节点的权值为 $mx$,此时是否合法。

根节点的子节点个数必然等于 $mx-P_l$。

那么现在可以提取出根节点的所有子节点所占用的 $P$ 数组下标,其权值必然分别是 $mx-P_l,mx-P_l+1,\cdots,mx-1$。

对于每个子节点,其在 $P$ 数组中后面的连续一段小于 $N-p_2$ 的数值,均为其子树对应的权值,此时递归进这一段判定其合法性,另外还要判定这一段数字是连续的一段数字,可以考虑使用 ST 表维护区间极差来做到这一点。

另外,还要判定按照根节点权值升序排序后相邻两个子树分别被 Takahashi 遍历得到的数组(是 $P$ 的子数组)满足字典序降序(一个是另一个的前缀则短的在前面),这一部分可以暴力维护,总复杂度可以发现并不会高于归并排序。

另外,还需要判定权值在 $[mx-P_l,mx-1]$ 之间的节点,其位置必须升序排列并且不能超出 $[l,r]$ 范围。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=2e5+5;
int n;
int p[maxn],pos[maxn];
struct ST{
    int mx[19][maxn],mn[19][maxn];
    void bd(){
        FOR(i,1,n){
            mx[0][i]=mn[0][i]=p[i];
        }
        FOR(i,1,18){
            FOR(j,1,n-(1<<i)+1){
                mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]);
                mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
            }
        }
    }
    int qmx(int l,int r){
        int _=__lg(r-l+1);
        return max(mx[_][l],mx[_][r-(1<<_)+1]);
    }
    int qmn(int l,int r){
        int _=__lg(r-l+1);
        return min(mn[_][l],mn[_][r-(1<<_)+1]);
    }
    int qrg(int l,int r){
        return qmx(l,r)-qmn(l,r);
    }
}st;
bool chk(int l,int r,int mx){
   // printf("[%d,%d, mx %d]\n",l,r,mx);
    int mn=p[l];
    vi rem;
    FOR(i,mn,mx){
        if(pos[i]>=l&&pos[i]<=r)rem.eb(pos[i]);
        else return 0;
    }
    for(int i=0;i+1<rem.size();i++){
        if(rem[i]>rem[i+1]){
            return 0;
        }
    }
    #define getlen(i) ((i+1==rem.size())?(r-rem[i]):rem[i+1]-rem[i]-1)
    for(int i=0;i<rem.size();i++){
        int len=getlen(i);
        if(len){
            if(st.qrg(rem[i]+1,rem[i]+len)+1!=len)return 0;
            if(chk(rem[i]+1,rem[i]+len,st.qmx(rem[i]+1,rem[i]+len))==0)return 0;
        }
        if(i+1<rem.size()){
            int len2=getlen(i+1);
            int s1=rem[i],s2=rem[i+1];
            bool ok=0;
            FOR(_,1,min(len,len2)){
                if(p[s1+_]-len!=p[s2+_]){
                    if(p[s1+_]-len<p[s2+_]){
                        return 0;
                    }
                    ok=1;
                    break;
                }
            }
            if(!ok){
                if(len>len2)return 0;
            }
        }

    }
    return 1;
}
void solve(int id_of_test){
    read(n);
    FOR(i,1,n){
        read(p[i]);
        pos[p[i]]=i;
    }
    st.bd();
    if(p[1]!=n){
        puts("No");
        return;
    }
    puts(chk(2,n,n-1)?"Yes":"No");
}
int main()
{
    int T;
    read(T);
    FOR(_,1,T){
        solve(_);
    }
    return 0;
}
/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*/

CF177G2 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-08 08:58:34

差不多就是对着题解复述了一遍,自己重新记录,没啥原创性,所以不申请题解了。

仅考虑单次询问怎么做。

令 $F_i$ 表示 $s$ 在 $f_i$ 中的出现次数,$G_i$ 表示 $f_i$ 中有多少 $s$ 跨越了 $f_{i-1}$ 与 $f_{i-2}$ 的交点。

那么,先预处理 $F_1,F_2$。

对于 $i \ge 3$,满足 $F_i = F_{i-1}+F_{i-2}+G_i$。

注意到 $G_i$ 的取值仅取决于 $f_{i-1}$ 的最后 $|s|-1$ 个字符,以及 $f_{i-2}$ 的最前的 $|s|-1$ 个字符。

有两个性质:

  • 若 $|f_i| \ge |s|$,那么 $f_{i+1}$ 与 $f_i$ 的前 $|s|$ 个字符一致。
  • 若 $|f_i| \ge |s|$,那么 $f_{i+2}$ 与 $f_i$ 的后 $|s|$ 个字符一致。

令 $x$ 表示最小的满足 $|f_x| \ge |s|$ 的整数。

那么对于 $\forall y \ge x+4$,满足 $G_y = G_{y-2}$。

注意到 $x$ 只有 $O(\log |s|)$ 级别,可以暴力计算。

对于 $\forall y \ge x+4$,满足 $F_y = F_{y-1}+F_{y-2}+G_{y} = F_{y-1}+F_{y-2}+G_{y-2}=F_{y-1}+F_{y-2}+(F_{y-2}-F_{y-3}-F_{y-4}) = F_{y-1}+2F_{y-2}-F_{y-3}-F_{y-4}$。

然后暴力算出 $F_x,F_{x+1},F_{x+2},F_{x+3}$,用矩阵快速幂暴力递推接下来的就可以了。

对于多次询问怎么办?

暴力预处理前 $35\sim 40$ 个 $f_i$,每次求 $x$ 都二分,其他过程就都一样了。

题解:AT_joisc2016_j 危険なスケート

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-23 15:41:40

考场上写出了正解,但是忘加前缀和优化,大样例不是极限数据还跑得挺快的,寄成了 65。

Solution

考虑对于点 $(i,j)$ 其能直接走到哪些点。

本文仅考虑向右走的情况,其他情况本质一样

假设 $(i,j)$ 右边第一个障碍位置是 $(i,x)$。

那么 $(i,j)$ 依次能走到:

  1. $(i,x-1)$。
  2. $(i,j+1)$。
  3. $(i,x-2)$。
  4. $(i,j+2)$。
  5. 以此类推。

对于上述第 $l$ 个点,$(i,j)$ 向其连接一条权值为 $l$ 的边。

直接这么建边就是对的,但是一次上述跳转,产生的新障碍可能会影响后面的其他跳转,为啥不会有问题?

因为如果一次横跳(就是上面描述的样子)之后,兜兜转转进行另一次横跳,并且和第一次横跳涉及到的节点交叉了,这种情况的答案一定不是最优的,算出的答案不会影响最优解,因为完全可以在第一次横跳的时候,在第一次横跳与第二次横跳交叉的节点直接开启第二次横跳。

按照上述建图方式会 TLE,还得优化。

注意到,$(i,j)$ 走到 $(i,j+1)$ 的权值为 $2$,走到 $(i,j+2)$ 权值为 $4$,以此类推走到 $(i,j+k)$ 权值为 $2k$。

然后 $(i,j)$ 走到 $(i,x-1)$ 的权值为 $1$,走到 $(i,x-2)$ 权值为 $3$,以此类推走到 $(i,x-k)$ 的权值为 $1+2(k-1)$。

上述若出现共同点则按照最小权值,不过直接建图最短路建出重边也无所谓。

这样优化:对于任意点 $(i,j)$ 满足 $(i,j)$ 和 $(i,j+1)$ 都不是障碍,那么 $(i,j)$ 向 $(i,j+1)$ 连一条权值为 $2$ 的双向边

对于 $(i,j)$ 其向 $(i,x-1)$ 连一条权值为 $1$ 的边即可。

明显地上述做法可以得到最优解,并且稍微画一下或想象一下图的样子可以发现这样绝对不会让答案算小。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
void io(){
	freopen("skate.in","r",stdin);
	freopen("skate.out","w",stdout);
}
const int maxn=1e3+5;
int n,m;
char s[maxn][maxn];
int r1,c1,r2,c2;
vector<pair<pii,int> > g[maxn][maxn];
int r_lstblk[maxn][maxn],r_nxtblk[maxn][maxn],c_lstblk[maxn][maxn],c_nxtblk[maxn][maxn];
bitset<maxn> vis[maxn];
int dis[maxn][maxn];
void dij(){
	memset(dis,0x3f,sizeof dis);
	priority_queue<pair<int,pii>,vector<pair<int,pii> >,greater<pair<int,pii> > > q;
	q.push(mkpr(0,mkpr(r1,c1)));
	dis[r1][c1]=0;
	while(!q.empty()){
		auto cur=q.top().se;
		q.pop();
		int x=cur.fi,y=cur.se;
		if(vis[x][y])continue;
		vis[x][y]=1;
		for(auto& v:g[x][y]){
			int fx=v.fi.fi,fy=v.fi.se,w=v.se;
			if(dis[fx][fy]>dis[x][y]+w){
				dis[fx][fy]=dis[x][y]+w;
				q.push(mkpr(dis[fx][fy],mkpr(fx,fy)));
			}
		}
	}
	if(dis[r2][c2]>1e8){
		puts("-1");
		return;
	}
	printf("%d\n",dis[r2][c2]);
}
void solve(int id_of_test){
	scanf("%d%d",&n,&m);
	FOR(i,1,n){
		scanf("%s",s[i]+1);
	}
	read(r1);
	read(c1);
	read(r2);
	read(c2);
	FOR(i,1,n){
		int lst=1;
		FOR(j,1,m){
			if(s[i][j]=='#')lst=j;
			else r_lstblk[i][j]=lst;
		}
		lst=m;
		REP(j,m,1){
			if(s[i][j]=='#')lst=j;
			else r_nxtblk[i][j]=lst;
		}
	}
	FOR(j,1,m){
		int lst=1;
		FOR(i,1,n){
			if(s[i][j]=='#')lst=i;
			else c_lstblk[i][j]=lst;
		}
		lst=n;
		REP(i,n,1){
			if(s[i][j]=='#')lst=i;
			else c_nxtblk[i][j]=lst;
		}
	}
	
	FOR(i,1,n){
		FOR(j,1,m){
			if(s[i][j]=='#')continue;
			if(s[i][j-1]!='#'){
				g[i][j].eb(mkpr(mkpr(i,j-1),2));
			}
			if(s[i][j+1]!='#'){
				g[i][j].eb(mkpr(mkpr(i,j+1),2));
			}
			if(s[i-1][j]!='#'){
				g[i][j].eb(mkpr(mkpr(i-1,j),2));
			}
			if(s[i+1][j]!='#'){
				g[i][j].eb(mkpr(mkpr(i+1,j),2));
			}
		}
	}
	FOR(i,1,n){
		FOR(j,1,m){
			if(s[i][j]=='#')continue;
			{
				int lst=c_lstblk[i][j]+1;
				g[i][j].eb(mkpr(mkpr(lst,j),1));
			}
			{
				int nxt=c_nxtblk[i][j]-1;
				g[i][j].eb(mkpr(mkpr(nxt,j),1));
			}
			{
				int lst=r_lstblk[i][j]+1;
				g[i][j].eb(mkpr(mkpr(i,lst),1));
			}
			{
				int nxt=r_nxtblk[i][j]-1;
				g[i][j].eb(mkpr(mkpr(i,nxt),1));
			}
		}
	}
	dij();
}
int main()
{
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?
2. 每一步操作,能否和自己的想法匹配?
3. 每一步操作的正确性是否有保证?
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?
*\/

CSP-S2025 游记 —— 前传

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-01 23:16:24

Day -?

GESP 毫无压力通过 8 级,做完题之后,因为不知道能提前离场,罚坐了 2h,玩了玩电脑上的软件。

结果笔试挂分了,不过无所谓。

CSPS1 Day -? SDOI2025

Day2 十几分,心态第一次被打崩,大概连续三四个月一直处于很难受的内耗状态。

不过我尽可能做到了和正常人看起来没有区别。

不过那段时间打比赛经常用一些奇奇怪怪的昵称,算是试图表达出来的唯一方式吧。

如果有人在核桃比赛的名单上看到过一些奇怪的名字,那可能就是我 :(

后来靠着我妈妈和小学一年级的妹妹的鼓励,慢慢才重新回到正常状态。

CSPS1 Day -?

南京 haoba 集训。

先是写了一堆大模拟,感觉代码能力上升了很多,至少应该不会再出现之前的会正解,但调不出代码的情况了。

接下来就是打 vjudge,一场好多题,ACM 赛制,经常一给就是一整个下午。

打的最好的一场是 rk2,但非 DP 场普遍打的不好。

打了一些 OI 赛制模拟,排名就稳定在 $7 \sim 10$。

题目最适合我的那场,由于二分上界漏打一个 $0$,挂没了一大半分数,糖丸了。

然后杭电多校打了 10 场,我在南京一中二队(虽然我不是南京一中的,但两个队友是)。队友是 chennie 和 5033,我感觉我就是个拖后腿的。

值得一提的是,当时住在 49 楼,南京的夜晚真的很繁华,可以看到玄武湖的各式灯光,各高楼大厦的彩色边框。

南京的闪电和山东的也不一样,南京的雷雨天能一秒一个闪电,而且还有过红色,蓝色,绿色的闪电,不太清楚这是什么原理,不过蓝色和绿色闪电是我妈妈看到的,我自己只见过红色的闪电(成功拍到了!)。

CSPS1 Day -20

意外得知,我 1 月份投的一道题被 TFXOI(没错,就是之前那个交互库出锅的 ”神“) 选中,参与了一场 Rated 比赛。

审核进度进行到一大半的时候,原负责人上学,我临时接替了背锅人的位置。

然后审核进度继续推进,过掉了审核,给了 Rated for everyone。

非常悲伤的一点是,我以为没啥问题了,结果 T4 撞题了。

说实话,作为当年 CF ”Your're wrong,here's why“ 的受益者,我真的希望再来一次 ,毕竟这次的影响小很多(总共 7 个人过),同样不是故意搬题。

但是还是被 Unrated 了,真是个悲伤的故事,不过 hos_lyric 来了,开心。

CSPS1 Day -14 左右

南京没有线下了,继续呆在那里,就必须一个人坐牢。

我坐了一两个周的牢,受不了了,回潍坊。

回去的第一场比赛就打成了答辩,虽说我的时间比别人少了 3/4,但 T1 没调出出来真的丢人。

CSPS1 Day -7

ABC 打了 6 个,没啥好说的。

核桃模拟,写了 S 组 T1 之后就跑了。

想着复赛模拟 100 分,就算初赛 100 分也拿不了奖,就没做初赛模拟。

获奖分数线出来,分数线是 101,早知道随便写道初赛模拟题了。

CSPS1 Day -2

模拟赛爆零了。

漏判了一种特殊情况,过了所有大样例,然后所有测试点里面都有一个这种情况(单个输入文件是多测)。

出题人说测试数据和大样例是一个 gen,我对此无法评价,毕竟同一个 gen 传不同参数也是同一个 gen。

核桃抽中了幸运奖,这下跟模拟赛 rp 平衡了。

CSPS1 Day -1

补完了刚回来的第一场比赛,感觉题目难度也不是真的高,之前怎么打成那样的?

CSP-S2025 复赛训练环节

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-01 23:17:51

CSPS1 Day1

我有 GESP 免考,所以不用准备考试。

上午听说要去打水痘疫苗,可是我之前得过水痘怎么还要打疫苗。

去医院,结果没带全资料,白跑了 /ll

总算得到特许吃了一次汉堡等。。。

下午,有点好奇如果我现在去做当年赛场没做出来的题,会怎么样。

选了 NOIP22 T3,然后一眼秒了,感叹当年赛时怎么不会这种水题。

然后写代码,发现不好写,DP 分类讨论有不少,wa 了很多次样例之后完成了修正。

然后提交过了 CCF 的数据,没过洛谷民间数据????

大战洛谷民间数据,最后发现是爆 long long 了。

CSP-S2 Day -39

模拟赛切了两题,本来以为大概是 $4 \sim 5$ 名,没想到因为没挂分拿了第一。

10 月

两天一场模拟赛,LCA 集训营特有的周二休息制。

打的比较的吃力,通常会拼全力过两题(也有可能不过 T2)。

排名也不稳定,挂分经常到 100+。

最差甚至可以到 rk19 左右。

我在此期间制定了一个较为稳定的比赛策略,至少保证了会的题能写完,但是挂分就有些难以避免。

截至 10.24 NOIP 模拟就上过 1 次 300 分,不过还是有一定下限的(min 值大于等于 150)。

这段时间我 vp 了一遍 CSP-S2021(我之前并没有写过 T2,T3),然后达到了当年的省队线(按照省队人数计算),但说这个有啥用呢?当年 CF 紫名都能进 SD 队,现在又是什么状况我很清楚。

值得一提的是有两场 ICPC 赛制组队比赛。

第一场倒数第二,第二场(实际上这场我是单挑的)倒数第三。

不过第二场好像差一点就能到现场 Ag 线了,我发现我可能一直高估了 ICPC 单挑拿 Regional Ag 的难度。

不过这些天,我一般到 15:00 的时候就会比较的困,因为我原本的回宿舍午休被取消了,不过大部分情况下17:00 可以打球,效率影响不大。

CSP-S2025 游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-01 23:19:16

CSP-S2025 游记——前传
CSP-S2025 复赛训练期间

10.30

打板子,写了 23.5 个板子(exgcd 完全版本不想写了)。

晚上玩了会贪吃蛇,被干死。

10.31

早上和 hjr 一起去出发点(振宁楼楼下),得到了他妈妈的一些水果 /bx。

然后稍微讨论了一下我前几天搞出来的一个简易的求点双内部边的方法的正确性。

然后再写了一个板子。

上车前自信不会晕车,然而上车之后晕的想吐,车上写题的计划也就不了了之,开了一局 Minecraft,发展到全套钻石。

晚上在摆烂,反正我自认为我已经做了充足准备。

我回去看了看我曾出到洛谷公开赛的题目,发现其中一道进入了至少 3 个不同机构的 vjudge 题单,感觉有些惊喜,被认可的感觉很好。

凌晨 1:00 的时候思考了一个听说很多人写了的题。

11.1

早上没有吃饭,担心吃太多出事,睡到了 11:00。

午饭吃了一点,同样担心吃太多出事。

继续睡觉,想了一道题维持状态。

中途弄丢了笔,找 wjr 大佬借了一只。

下午入场考试。

考前 20min 来到现场,我终究没逃过,感觉到极度困倦,不过我知道目前最重要的是打板子,用了 10min 复刻了我日常使用的板子。

开始考试,看了下 T1,这不是一眼贪心,5min 写完,10min 测大样例 + 检查。

然后感觉我还是很困,就干脆喝下了全部 4 瓶红牛,吃掉了全部的巧克力。

阅读了一下 T2 题意,然后犯下了我本场比赛最大的错误。

我迅速列出了两条结论:

  • 对于给定的 $m$ 条边,只有 MST 中的 $n-1$ 条才有用。
  • 对于同一个新点来说,将任意两个原图点合并的代价永远是自己到这两个点距离之和

结论一没问题,但是结论二明显假的,但是我就是认为是对的。

我权衡了一下认为可以写,就试图实现。

然后我思考了一段时间发现很难写,遂改为实现 80pts 做法。

我写完之后,先是通过了小样例和第一个大样例,然后 wa 在第二个大样例。

此时,我还以为只是写挂了,因为大样例 1,2 的差别仅仅是 $k=5$ 还是 $k=10$。

殊不知我的做法 tmd 就是完全假的!!!!

我调试了 1h,修正了一些细节问题,但是 sample #3 的输出却只是在正确的答案上下徘徊。

我心态有些炸了,去上了个厕所回来。

我选择了看 T3,T4,发现 T4 送了 20,写了。

回来看 T2,继续调了 0.5h,此时我想着要不重新分析一下。

然后发现做法假了。

我强迫自己冷静,开始重构,顺带同时思考 T3。

然后经过一段时间的思考,我又搞出来一个看起来很靠谱的 80pts 做法,我用了 20min 思考怎么优化到 100,然后失败了。

然后写 80pts 做法,此时剩余时间40min。

我打算豪赌我能写出 T2 80pts。

我失败了,最终还是未能通过 Sample #3,#4。

高一的第一场比赛就此结束,心有不甘。

题解:P11762 [IAMOI R1] 走亲访友

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-12 21:19:39

注意到 $k \ge n+m$。

如果本题给定的图是欧拉图,那么可以在一次旅行中遍历一遍所有的边,也就意味着可以自由决定给定的所有边中保留哪些边。

此时,只需要 dfs 一遍找出一个生成树(此处默认 $s$ 为根),然后删除所有非树边,即可解决本题。

但是给定图并不保证是欧拉图。

但是题目并没有禁止我们多次遍历同一条边,也就是说可以添加不超过 $n$ 条额外的重边

考虑对于要构建的那个生成树,对其从根开始递归处理,处理完当前节点的子树之后,若当前节点度数是奇数,那么向自己的父节点连一条边使得当前节点度数变为偶数。

但是根节点没有父节点怎么办?注意到若除了根节点以外的所有节点度数都为偶数了,那么根节点度数一定也是偶数,因为一条边对图的总度数贡献是 $2$,也就是说图的总度数为偶数。

无向图欧拉回路的一个简单的写法,可以考虑 set 维护所有出边,每搜索一个点,就将这条边从两个端点的出边列表删除,时间复杂度 $O(m \log m)$。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=1e3+5;
int n,m,k,s;
vector<pii> g[maxn];
multiset<pii> g2[maxn]; 
bool ontree[maxn*maxn];
int deg[maxn];
bool vis[maxn];
void dfs1(int u,int _fa,int ine){
	vis[u]=1;
	for(auto& tmp:g[u]){
		if(!vis[tmp.fi]){
			ontree[tmp.se]=1;
			dfs1(tmp.fi,u,tmp.se);
		}
	}
	if(deg[u]&1)g2[u].insert(mkpr(_fa,ine)),g2[_fa].insert(mkpr(u,ine)),deg[u]++,deg[_fa]++;
}
stack<pii> ans;
int cur[maxn];
void dfs2(int u){
	while(!g2[u].empty()){
		auto tmp=*g2[u].begin();
		g2[u].erase(g2[u].begin());
		g2[tmp.fi].erase(g2[tmp.fi].find(mkpr(u,tmp.se)));
		dfs2(tmp.fi);
		ans.push(mkpr(tmp.se,ontree[tmp.se]));
	}
}
void solve(int id_of_test){
	read(n);
	read(m);
	read(k);
	read(s);
	FOR(i,1,m){
		int u,v;
		read(u);
		read(v);
		g[u].eb(mkpr(v,i));
		g[v].eb(mkpr(u,i));
		deg[u]++,deg[v]++;
	}
	FOR(u,1,n){
		for(auto& tmp:g[u])g2[u].insert(tmp);
	}
	dfs1(s,0,0);
	dfs2(s);
	printf("%d\n",int(ans.size()));
	while(!ans.empty()){
		printf("%d %d\n",ans.top().fi,ans.top().se);
		ans.pop(); 
	}
}
int main()
{
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}

ABC420F 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-27 11:09:49

好像就我用了这个奇怪的 $O(nm\lpha(n) + m \log m)$ 做法,场上把两处 $n,m$ 写反没调出来。

前置知识

并查集,树状数组,单调队列。

Solution

考虑枚举矩形的最左侧的那一列,统计对应的合法矩形个数,最后相加即可。

令 $l_{i,j}$ 表示从格子 $(i,j)$ 开始,向右延申最多多少格子,不会碰到障碍,格子 $(i,j)$ 也计入统计。

那么,假设枚举到的是 $(x,c),(x+1,c),\cdots,(y,c)$,那么,假如不考虑矩形面积小于等于 $k$ 的限制,那么对应的合法矩形个数将是 $\min\limits_{i=x}^y l_{i,c}$。

也就是说,在不考虑面积限制的情况下,一种枚举方案对应的矩形合法方案数仅取决于其 $l$ 值最小的格子

那么,考虑枚举这个 $l$ 值最小的格子,计算出有多少种合法的枚举左侧第一列的方案,使得这个格子是 $l$ 值最小的,那么假设不考虑面积限制,就可以直接得出答案了。

此处实现有一些简单的技巧:若同一列多个格子的 $l$ 值一致,那么行数更小的格子视作 $l$ 值更小;可以使用单调队列均摊 $O(1)$ 求出每个格子的同一列最近的 $l$ 值更小的格子。

现在考虑面积小于等于 $k$ 的限制。

假设枚举了 $(x,y)$ 作为 $l$ 值最小的格子,令其 $l$ 值为 $p$,且 $(l-1,y),(r+1,y)$ 分别是 $y$ 列中,与 $(x,y)$ 最近的行数更小且 $l$ 值更小的格子,与 $(x,y)$ 最近的行数更大且 $l$ 值更小的格子。

那么,原本没有面积限制的情况下,任意选择 $l \le a \le b \le r$,选中 $y$ 列的 $a$ 到 $b$ 行的格子都是一种合理的枚举方案。

但是若 $\lfloor \frac{b-a+1}{k}\rfloor \lt p$,那么实际上枚举 $(a,y),(a+1,y),\cdots,(b,y)$ 对应的合法矩形方案数仅会受到 $b-a+1$ 的值的约束,而不是当前 $(x,y)$ 的 $l$ 值 $p$ 的约束。

考虑先只统计 $\lfloor \frac{b-a+1}{k}\rfloor \ge p$,也就是 $b-a+1 \le \lfloor\frac{p}{k}\rfloor$ 的情况。

::::info[现在需要求解一个这样的问题] 有 $a+b-1$ 个从左到右排列的格子,一个非负整数 $lim$。

称从左边开始第 $a$ 个格子为中心格子。

那么现在需要选择不超过 $lim$ 个连续格子使得包含中心格子,问有多少种方案。
::::
:::::info[解决方案] 令 $x$ 表示从中心格子开始向左边数,一共有多少个格子被选中了。。

现在,需要对于 $\forall x \in [1,a]$,计算出对应的合法的右端点的个数。

可能存在一部分 $x$,其右端点的可选集合必然是 $[a,a+b-1]$,要满足这个,则 $x+b-1 \le lim$,即 $x \le lim-b+1$。

那么这一部分 $x$ 的总贡献就是 $\max(0,lim-b+1)\cdot b$。

对于 $x \gt lim-b+1$,其贡献将会是 $\max(0,lim-x+1)$。

这一部分的最小 $x$ 将会是 $\max(0,lim-b+1)+1$,最大的 $x$ 将会是 $\min(lim,a)$,分别求出贡献值,然后这一部分 $x$ 的贡献的累加形式是一个等差数列,刚才求解了最大值最小值,并且公差为 $1$,直接算总和就行了。

时间复杂度 $O(1)$。
:::::

最后,需要统计有多少种选择方案 $(a,y),(a+1,y),\cdots,(b,y)$ 满足 $\lfloor\frac{k}{b-a+1}\rfloor \lt \min\limits_{i=a}^b l_{i,y}$,每种此类方案都会再贡献 $\lfloor\frac{k}{b-a+1}\rfloor$ 的答案。

考虑从小到大枚举 $b-a+1$ 的值,计算有多少选择同一列连续 $b-a+1$ 行的方案,使得不覆盖任意一个 $l$ 值小于等于 $b-a+1$ 的点。

随着 $b-a+1$ 的增大,原来能覆盖的点不会变得不能覆盖,所以可以每次暴力更新能覆盖的点。

然后,对于一列中长度为 $len$ 的极大的连续可覆盖格子,其贡献的选择方案数是 $len-\lfloor\frac{k}{b-a+1}\rfloor+1$。

那么,考虑定义两个数组 $sum,cnt$,每个 $len$ 都会导致 $sum_{len} \leftarrow sum_{len}+len,cnt_{len}\leftarrow cnt_{len}+1$。

那么,当前 $b-a+1$ 对应的选择方案数就是:
$\sum\limits_{i=b-a+1}^n (sum_{i}-cnt_{i}\cdot(b-a)) = \sum\limits_{i=b-a+1}^nsum_i - (\sum\limits_{i=b-a+1}^{n}cnt_i)(b-a)$。

这个选择方案数乘上 $\lfloor\frac{k}{b-a+1}\rfloor$ 就是这个 $b-a+1$ 对应的答案总贡献了。

$sum,cnt$ 都可以用树状数组维护,同一列的极大连续可覆盖格子则可以用并查集维护。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=5e6+5;
struct UNIONFIND{
    int fa[maxn],siz[maxn];
    void init(){
        FOR(i,1,maxn-1)fa[i]=i,siz[i]=0;
    }
    int get(int x){
        return x==fa[x]?x:fa[x]=get(fa[x]);
    }
    void ms(int a,int b){
        if(get(a)!=get(b)){
            siz[get(b)]+=siz[get(a)];
            fa[get(a)]=get(b);
        }
    }
}uf;
struct BIT{
    ll sum[500005];
    int cnt[500005];
    int lowbit(int x){
        return x&-x;
    }  
    void add(int pos,int _v){
        for(;pos<=500004;pos+=lowbit(pos)){
            sum[pos]+=_v;
            cnt[pos]++;
        }
    }
    void del(int pos,int _v){
        for(;pos<=500004;pos+=lowbit(pos)){
            sum[pos]-=_v;
            cnt[pos]--;
        }
    }
    ll _qsum(int pos){
        ll res=0;
        for(;pos;pos-=lowbit(pos)){
            res+=sum[pos];
        }
        return res;
    }
    ll qsum(int l,int r){
        return _qsum(r)-_qsum(l-1);
    }
    int _qcnt(int pos){
        int res=0;
        for(;pos;pos-=lowbit(pos)){
            res+=cnt[pos];
        }
        return res;
    }
    int qcnt(int l,int r){
        return _qcnt(r)-_qcnt(l-1);
    }
}tree;
ll gas(ll s,ll t){
    return (s+t)*(t-s+1)\/2;
}
ll calc(ll lflen,ll rglen,ll lim){
    \/\/ 包括中点的左半部分 =lflen,包括中点的右半部分=rglen
    \/\/ lim:长度上限,要求包括中点
    \/\/ rglen+lfuse-1 <= lim
    \/\/ lfuse <= lim-rglen+1
    ll lfuse=lim-rglen+1;\/\/ 左边使用了 lfuse 个或以下时,右边可以填满
    ckmn(lfuse,lflen);
    ll ans=0;
    if(lfuse>0){
        ans+=rglen*lfuse;
    }
    ckmx(lfuse,0ll);

    \/\/ 左边使用 lfuse 以上的时候,将会限制长度为 lim
    ll lfmx=min(lflen,lim);\/\/ 左边最多选择多长
    \/\/ 左边选择 lfuse+1 的话,有 lim-(lfuse+1)
    if(lim-lfmx+1<=lim-(lfuse+1)+1){
        ans+=gas(lim-lfmx+1,lim-(lfuse+1)+1);
    }
    return ans;
}
vector<pii> ids[500005];
void solve(int id_of_test){
    int n,m,k;
    read(n);
    read(m);
    read(k);
    auto id=[&](int i,int j)->int{
        return (i-1)*m+j;
    };
    char s[n+5][m+5];
    FOR(i,1,n){
        scanf("%s",s[i]+1);
    }
    int rg[n+5][m+5];
    FOR(i,1,n){
        rg[i][m+1]=0;
        REP(j,m,1){
            if(s[i][j]=='.'){
                rg[i][j]=rg[i][j+1]+1;
            }else{
                rg[i][j]=0;
            }
        }
    }
    ll ans=0;
    FOR(col,1,m){
        int lef[n+5],rig[n+5];
        stack<int> stk;
        FOR(row,1,n){
            while(!stk.empty()&&rg[row][col]<rg[stk.top()][col]){
                stk.pop();
            }
            if(stk.empty())lef[row]=1;
            else lef[row]=stk.top()+1;
            stk.push(row);
        }
        while(!stk.empty())stk.pop();
        REP(row,n,1){
            while(!stk.empty()&&rg[row][col]<=rg[stk.top()][col]){
                stk.pop();
            }
            if(stk.empty())rig[row]=n;
            else rig[row]=stk.top()-1;
            stk.push(row);
        }
        FOR(row,1,n){
            if(rg[row][col]==0)continue;
            
            int lenlim=k\/rg[row][col];\/\/ k\/lenlim = rg[row][col],将会是最高限制
            ans+=calc(row-lef[row]+1,rig[row]-row+1,lenlim)*rg[row][col];
         \/\/   printf("(%d,%d): [%d,%d] lenlim %d calc %d\n",row,col,lef[row],rig[row],k\/rg[row][col],calc(row-lef[row]+1,rig[row]-row+1,lenlim));
        }
    }
    
    FOR(i,1,n){
        FOR(j,1,m){
            if(rg[i][j])ids[rg[i][j]].eb(mkpr(i,j));
        }
    }
  \/\/  printf("preans %lld\n",ans);
    FOR(len,1,n){
        {
            if(k\/len==0)break;
            int up,dw;
            if(len==1)up=m;
            else up=k\/(len-1);
            dw=k\/len+1;
            ckmn(up,m);
        \/\/    printf("len %d add [%d,%d]\n",len,dw,up);
            \/\/ 所有 rg 小于等于 k\/len 的格子都不能放,
            \/\/ 所有大于当前 m\/len 限制的,全部都可以放进去了
            REP(i,up,dw){
                for(auto [x,y]:ids[i]){
                    if(x>=2){
                        if(uf.siz[uf.get(id(x-1,y))]){
                            tree.del(uf.siz[uf.get(id(x-1,y))],uf.siz[uf.get(id(x-1,y))]);
                        }
                    }
                    if(x<n){
                        if(uf.siz[uf.get(id(x+1,y))]){
                            tree.del(uf.siz[uf.get(id(x+1,y))],uf.siz[uf.get(id(x+1,y))]);
                        }
                    }
                    if(x>=2&&uf.siz[uf.get(id(x-1,y))]){
                        if(uf.siz[uf.get(id(x-1,y))]){
                            uf.ms(uf.get(id(x,y)),uf.get(id(x-1,y)));
                        }
                    }
                    if(x<n&&uf.siz[uf.get(id(x+1,y))]){
                        if(uf.siz[uf.get(id(x+1,y))]){
                            uf.ms(uf.get(id(x,y)),uf.get(id(x+1,y)));
                        }
                    }
                    uf.siz[uf.get(id(x,y))]++;
                    tree.add(uf.siz[uf.get(id(x,y))],uf.siz[uf.get(id(x,y))]);
                }
            }
            ans+=(tree.qsum(len,n)-1ll*tree.qcnt(len,n)*(len-1))*(k\/len);
        }
    }
    printf("%lld\n",ans);
}
int main()
{
    uf.init();
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/

ABC420G 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-08-27 11:29:31

任意合法的 $n$ 必然满足:
$$ n^2+n+x = (n+k)^2,k\in \mathbb{Z} $$

考虑枚举这个 $k$,粗略估算 $|k| \le 10^7 + 100$。

那么 $(n+k)^2-n^2-n=x$。

$n^2+2nk+k^2-n^2-n=x$。

$2nk-n = x-k^2$。

$(2k-1)n=x-k^2$。

$n = \frac{x-k^2}{2k-1}$。

然后本题就做完了。

题解:CF1003F Abbreviation

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-12 11:42:57

本做法来源于一份 chemthan 在 Codeforces 上的提交,原作者的实现是 $O(n^4)$ 的,但是我发现可以将其优化到 $O(n^2)$ 从而爆标。

这份代码的思路是预处理每个子区间的左边最近的不相交的相等区间。

预处理过程,则枚举子区间的左端点 $x$,同时将所有右端点在 $x$ 左边的子区间加入字典树,对于每个字典树上的节点,记录其代表的区间在数组中最右边的出现位置。

然后,升序枚举当前子区间的右端点 $y$,通过字典树可以快速查询 $[x,y]$ 的左边最近的出现位置。

虽然字典树单词查询一个字符串的复杂度是 $O(|S|)$ 的,但是这里,前一个被查询的字符串是后一个被查询的字符串的前缀,所以后一个字符串从前一个字符串对应的末尾节点开始查找就可以了,所以复杂度等于最后一个字符串的长度。

完成预处理之后,采用 DP,令 $f_{l,r}$ 为最右边的区间是 $[l,r]$ 时的答案。

转移的话,上一个选择的区间肯定是自己左边最近的相等区间,这个已经预处理出来了。

本做法的预处理部分可以做到 $O(n^2)$,但是作者写成了 $O(n^4)$,所以我对作者的代码进行了修改。

DP 部分也可以做到 $O(n^2)$,所以总的时间复杂度是 $O(n^2)$ 的。

但是,空间复杂度仍然是 $O(n^3)$ 的,原因是最多 $O(n^2)$ 个节点,每种节点都有 $n$ 种可能的连边,所以字典树的 nxt 数组得开到 $n^2 \times n$。

但是注意到实际上只会使用 $O(n^2)$ 的空间。

所以,如果想要降低空间复杂度的话,可以考虑开 unordered_map 或者 map 等。

如果开 map 的话,就只有 $O(n^2)$ 的空间占用和 $O(n^2 \log n)$ 的时间复杂度,仍然优于官方 std。

\/\/ by chemthan
#include <bits\/stdc++.h>
using namespace std;

const int N = 305;                    \/\/ max number of words
const int SIG = N;                    \/\/ alphabet size (distinct words)
const int T = N * (N + 1) \/ 2 + 5;    \/\/ upper bound of trie nodes per build

int n, m;
int id[N], lenw[N];
string w[N];

int f[N][N], dp[N][N];               \/\/ f[i][j]: previous start of block equal to [i..j]
int nxt[T][SIG], val[T], ptr;        \/\/ trie
long long pref[N];                   \/\/ prefix sum of word lengths

unordered_map<string, int> mp;

inline int get_id(const string &s) {
    auto it = mp.find(s);
    if (it != mp.end()) return it->second;
    return mp[s] = ++m;
}
int rem[N];
inline void add(int l, int r) {
    int u = 0;
    for (int i = l; i <= r; ++i) {
        if (!nxt[u][id[i]]) nxt[u][id[i]] = ++ptr;
        u = nxt[u][id[i]];
        val[u] = max(val[u], l);
    }
	rem[l]=u;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> w[i];
        id[i] = get_id(w[i]);
        lenw[i] = (int)w[i].size();
        pref[i] = pref[i - 1] + lenw[i];
    }

    int total = (int)pref[n] + (n - 1);      \/\/ original length with spaces
    int ans = total;

    \/\/ prepare f with -1
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) f[i][j] = -1;
    }

    \/\/ build f: for each split point i, trie of all segments ending at i-1
    for (int i = 2; i <= n; ++i) {
        for (int l = 1; l < i - 1; ++l){
			int u=rem[l];
			if(!nxt[u][id[i-1]])nxt[u][id[i-1]]=++ptr;
			u=nxt[u][id[i-1]];
            val[u]=max(val[u],l);
			rem[l]=u;
		}
		add(i-1,i-1);
        int u = 0;
        for (int r = i; r <= n; ++r) {
            int v = nxt[u][id[r]];
            if (!v) break;
            u = v;
            f[i][r] = val[u];                 \/\/ previous start pv of a block equal to [i..r]
        }
    }

    \/\/ DP count of non-overlapping equal segments ending at [i..j]
    for (int i = 1; i <= n; ++i) {
        for (int j = i; j <= n; ++j) {
            int pv = f[i][j];
            dp[i][j] = 1;
            if (pv != -1) dp[i][j] = dp[pv][pv + (j - i)] + 1;

            if (dp[i][j] > 1) {
                \/\/ saving per occurrence: (sum len) + spaces - initials
                \/\/ = (pref[j]-pref[i-1]) + (j-i) - (j-i+1) = (pref[j]-pref[i-1]) - 1
                int save_one = (int)(pref[j] - pref[i - 1]) - 1;
                ans = min(ans, total - save_one * dp[i][j]);
            }
        }
    }

    cout << ans << '\n';
    return 0;
}
共 318 篇博客