T1T1 我逝了
Description
Solution
一些闲话:这题真的给我做泪目了,现在想起我只觉得自己是个 ntnt,建一个空间常数大的 samsam,而且还只能 dfsdfs 搞出那张 dagdag,最后写出的代码像是在给人喂 💩,以 MLEMLE 和 TLE 零分的优势取得了辉煌的战绩 😢。
建出 ac 自动机,利用路径压缩求出 n 个串的偏序集,然后就是 [CTSC 2008] 祭祀 了,时间复杂度 O(n3).
Code
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <queue>
# include <vector>
# include <cstring>
# include <iostream>
using namespace std;
const int maxn = 2005;
const int maxl = 1e7+5;
const int infty = 0x3f3f3f3f;
bool vis[maxn]; int fa[maxl];
char s[maxl]; int fail[maxl];
queue <int> q; bool g[maxn][maxn];
int dep[maxn], Ref[maxl], ed[maxn];
int n, head[maxn], cnt=-1, S, T, arc[maxn];
struct edge { int nxt,to,w; } e[int(5e6)];
void addEdge(int u,int v,int w) {
e[++cnt].to=v, e[cnt].nxt=head[u],
e[cnt].w=w, head[u]=cnt;
e[++cnt].to=u, e[cnt].nxt=head[v],
e[cnt].w=0, head[v]=cnt;
}
bool bfs() {
for(int i=1;i<=T;++i)
dep[i]=infty, arc[i]=-1;
while(!q.empty()) q.pop(); int v;
q.push(S), arc[S]=head[S], dep[S]=0;
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=head[u]; ~i; i=e[i].nxt)
if(e[i].w && dep[v=e[i].to]==infty) {
dep[v] = dep[u]+1,
arc[v]=head[v], q.push(v);
if(v==T) return true;
}
} return false;
}
int dfs(int u,int canFlow) {
if(u==T) return canFlow;
int sumFlow=0, d, v;
for(int i=arc[u]; ~i; i=e[i].nxt) {
arc[u] = i;
if(e[i].w && dep[v=e[i].to]==dep[u]+1) {
d = dfs(v, min(canFlow,e[i].w));
if(!d) dep[v] = infty;
e[i].w -= d, e[i^1].w += d,
canFlow -= d, sumFlow += d;
if(!canFlow) break;
}
} return sumFlow;
}
int dinic() {
int ret=0; while(bfs())
ret+=dfs(S,infty); return ret;
}
namespace mac {
int t[maxl][2], idx;
void ins(int now) {
int p=0, len=strlen(s+1);
for(int i=1;i<=len;++i) {
bool d = (s[i]=='a'?0:1);
if(!t[p][d]) t[p][d]=++idx, fa[idx]=p;
p = t[p][d];
} ed[now]=p, Ref[p]=now;
}
void getfail() {
while(!q.empty()) q.pop();
for(int i=0;i<2;++i)
if(t[0][i]) q.push(t[0][i]);
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=0;i<2;++i)
if(!t[u][i]) t[u][i]=t[fail[u]][i];
else fail[t[u][i]]=t[fail[u]][i], q.push(t[u][i]);
} Ref[0]=-1;
}
}
void findS(int u) {
if(vis[u]) return; vis[u]=1;
for(int i=head[u]; ~i; i=e[i].nxt)
if(!e[i].w && e[i].to!=T) findS(e[i].to);
}
bool inside(int u) {
return !vis[u+n] && vis[u];
}
int main() {
n=read(9); S=(n<<1)+1, T=S+1;
memset(head,-1,sizeof(int)*(T+2));
for(int i=1;i<=n;++i)
scanf("%s",s+1), mac::ins(i);
mac::getfail(); vector <int> vec;
for(int i=1;i<=n;++i)
for(int j=ed[i]; j; j=fa[j]) {
vec.clear(); int x=fail[j];
for(; !Ref[x]; x=fail[x])
vec.emplace_back(x);
for(; !vec.empty(); vec.pop_back())
fail[vec.back()] = x;
fail[j] = x;
if(j!=ed[i] && Ref[j]) x=j;
if(x) g[i][Ref[x]]=1;
}
for(int k=1;k<=n;++k) for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) g[i][j] |= (g[i][k]&g[k][j]);
for(int i=1;i<=n;++i)
addEdge(S,i+n,1), addEdge(i,T,1);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
if(g[i][j]) addEdge(i+n,j,1);
int M = dinic(); print(n-M,'\n');
for(int i=1;i<=n;++i) if(!vis[i])
if(e[(i<<2)-2].w) findS(i);
for(int i=1;i<=n;++i)
if(inside(i)) print(i,' '); puts("");
return 0;
}
T2 排列数列
Description
Solution
好神啊 qwq.
引入 ∘ 符号,其中 a∘b 表示排列 pi=abi,接下来我们阐述 ∘ 运算的一些性质:
- ∘ 运算满足结合律;
- 令 p−1 为 p 的逆,那么有 p∘p−1=p−1∘p=ϵ;
- (p∘q)−1=q−1∘p−1,这个可以推导一下 —— 记 fi=p∘q,gi=q−1∘p−1,那么 f∘g=p∘q∘q−1∘p−1=ϵ,这利用了上文的结合律。
于是题目中的 f 函数可以被刻画成 f(p,q)pi=qi→f(p,q)∘p=q→f(p,q)=q∘p−1。由此可以进行递推:
由于 k 很大,所以尝试找一下规律 —— 观察到 a5,a6 都有一个共同的较大的一坨,不妨令其为 A=q∘p−1∘q−1∘p,那么
由于 A−1=p−1∘q∘p∘q−1,所以 a8=A∘q∘A−1.
事实上,a7,a8 开始与末尾的 A,A−1 结构可以进行抵消
我们惊奇地发现,中间的 q∘p−1 就是 a3!而 a7,a8 中间的部分就是 a1,a2。所以,我们可以利用此递推再加上快速幂解决此题,复杂度 O(nlogk).
Code
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp] = x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
const int maxn = 1e5+5;
int n, k;
struct perm {
int a[maxn];
perm operator * (const perm& t) const {
perm r; for(int i=1;i<=n;++i)
r.a[i]=a[t.a[i]]; return r;
}
void output() { for(int i=1;i<=n;++i) print(a[i],' '); }
void readPerm() { for(int i=1;i<=n;++i) a[i]=read(9); }
} p, q, ans, A;
perm inv(const perm& t) {
perm r; for(int i=1;i<=n;++i)
r.a[t.a[i]]=i; return r;
}
perm qkpow(perm t,int y) {
perm r; for(int i=1;i<=n;++i) r.a[i]=i;
for(; y; y>>=1, t=t*t)
if(y&1) r=r*t; return r;
}
int main() {
n=read(9), k=read(9)-1;
p.readPerm(), q.readPerm();
A = q*inv(p)*inv(q)*p;
switch(k%6) {
case 0: ans=p; break;
case 1: ans=q; break;
case 2: ans=q*inv(p); break;
case 3: ans=q*inv(p)*inv(q); break;
case 4: ans=A*inv(q); break;
case 5: ans=A*p*inv(q); break;
}
ans = qkpow(A,k/6)*ans*qkpow(inv(A),k/6);
ans.output();
return 0;
}
T3 同步子序列
Description
Solution
这次真的是复读题解了。
设 b 为 +1,a 为 −1,那么在序列中找出若干段前缀和为零的前缀,可以将序列划分成若干段互不影响的区间。我们分开考虑每一段,这有什么好处呢?由于一段的每个前缀和都不为零,所以一定有所有前缀和均大于零或小于零,也就是说对于一对 a,b,在同一段内一定满足 a 出现在 b 之前或 b 出现在 a 之前。对这两种情况分类讨论:
-
a 出现在 b 之前。容易得知第一个字符一定为 a,考虑这个 a 匹配的 b,显然会将它们之间的所有 a 都删掉以保证字典序最大。递归进行此操作即可;
-
b 出现在 a 之前。最终序列一定是一段 b,考虑最大化其长度。可以求出最大前缀和,记其为 val,且匹配到的 b 的位置为 p。这里有个结论:
最优解一定是只删除 p 之前的 a,保留后面的所有字符。
首先 p 后面紧挨的一段 a 肯定不能被删除,否则不满足最大前缀和的约束。紧接着后面就是 b,而删除这个 b 是不优的。结论得证。
由于可能有多个位置 p1,p2 都满足前缀和为 val 的条件,且后面部分不会被删掉了,所以可以直接比较 (p1,n],(p2,n] 来得知选哪个字典序更大。
最后考虑将区间合起来,不过选取所有区间并不见得是最优解。所以还要维护一个类似单调栈的东西,每次判断一下新加入字符串 s 和栈顶接上 s 的字典序大小关系。时间复杂度 O(n2).
Code
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp] = x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <iostream>
using namespace std;
const int maxn = 6005;
char s[maxn];
string S[maxn], t;
int n, presum, pos, tp;
void update(int st,int r) {
if(pos==-1) return pos=st, void();
for(int i=1;i<=r-st+1;++i)
if(s[i+pos-1]>s[i+st-1]) return;
else if(s[i+pos-1]<s[i+st-1]) return pos=st, void();
}
void makeString(int l,int r) {
t="", pos=-1; int j, val=0, cur=0, tot=0;
if(s[l]=='a') {
for(int i=l;i<=r;++i) if(s[i]^'b') {
j=i; int tmp=tot;
while(233) { ++ j;
if(s[j]=='a') ++tot;
else if(!tmp) break;
else --tmp, --tot;
} t+='a', t+='b', i=j;
} else --tot;
} else {
for(int i=l;i<=r;++i)
cur += (s[i]=='a'? -1: 1),
val = max(val, cur); cur=0;
for(int i=l;i<=r;++i) {
cur += (s[i]=='a'? -1: 1);
if(cur==val) update(i+1,r);
}
for(int i=1;i<=val;++i) t+='b';
for(int i=pos;i<=r;++i) t+=s[i];
}
}
bool cmp(const string& s,const string& t) {
int len = t.length();
for(int i=0;i<len;++i)
if(s[i]<t[i]) return true;
else if(s[i]>t[i]) return false;
return false;
}
int main() {
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n=(read(9)<<1), scanf("%s",s+1);
for(int i=1, j=1; i<=n; ++i) {
presum += (s[i]=='b'? 1: -1);
if(presum==0) makeString(j,i);
else continue; j = i+1;
while(tp && cmp(S[tp]+t,t)) --tp;
S[++tp] = t;
}
for(int i=1;i<=tp;++i) cout << S[i]; puts("");
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
2021-07-12 Codeforces Global Round 12