OIFHA251108(成都嘉祥)
吐槽
虽然难,但全部都是比较好玩的题目(除了 \(T_1\))。
T1
幸好没做这题(doge)。
其核心思想在于看到有向图以及每条边可以走很多次且只算一次需要很快想到 tarjan,为什么要很快?因为你还要调代码。
然后这是一个 DAG,跑一个原图的拓扑和一个反图的拓扑就行了,排完序后,最后计算答案的时候双指针即可。
具体看代码吧
//注意16Mib以及模数为10^8+7
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#define int long long
#define PII pair<int,int>
#define N 2505
using namespace std;
struct edge{
int v,w;
};
int fa[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x,int y) {
int xx = find(x),yy = find(y);
if (xx != yy) fa[xx] = fa[yy];
}
const int mod = 1e8 + 7;
int qpow(int a,int b) {
int res = 1;
while(b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int dfn[N],low[N],tot,cnt,val[N],bel[N],ltk[N],col;
bool in_stack[N];
stack<int> sta;
vector<int> scc[N];
vector<edge> g[N];
vector<edge> g2[2][N];
int du[2][N];
void tarjan(int cur) {
ltk[cur] = col;
in_stack[cur] = 1;
dfn[cur] = low[cur] = ++cnt;
sta.push(cur);
for (auto i : g[cur])
if (!dfn[i.v]) tarjan(i.v),low[cur] = min(low[cur],low[i.v]);
else if (in_stack[i.v]) low[cur] = min(low[cur],dfn[i.v]);
if (dfn[cur] == low[cur]) {
tot ++;
int t = sta.top();
do{
t = sta.top();
in_stack[t] = 0;
scc[tot].push_back(t);
bel[t] = tot;
sta.pop();
}while(t != cur);
}
}
int n,m;
int f[2][N];
bool vis[N];
vector<int> ans[2][N];
vector<int> sum[2][N];
int ssum[2][N];
void solve() {
while(!sta.empty()) sta.pop();
for (int i = 1;i <= n;i ++) ltk[i] = ssum[0][i] = ssum[1][i] = vis[i] = 0,sum[0][i].clear(),sum[1][i].clear(),ans[0][i].clear(),ans[1][i].clear(),g[i].clear(),dfn[i] = low[i] = 0,in_stack[i] = 0,scc[i].clear(),fa[i] = val[i] = 0,g2[0][i].clear(),g2[1][i].clear();
col = tot = 0,cnt = 0;
scanf("%lld%lld",&n,&m);
for (int i = 1;i <= m;i ++) {
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
g[u].push_back({v,w});
}
for (int i = 1;i <= n;i ++)
if (!dfn[i]) col ++,tarjan(i);
for (int i = 1;i <= n;i ++)
for (auto j : g[i]) {
int v = j.v,w = j.w;
if (bel[v] == bel[i]) val[bel[v]] += w;
else g2[0][bel[i]].push_back({bel[v],j.w}),du[0][bel[v]] ++,g2[1][bel[v]].push_back({bel[i],j.w}),du[1][bel[i]] ++;
}
for (int i = 1;i <= tot;i ++) fa[i] = i;
for (int type = 0;type < 2;type ++) {//0->z,1->f
queue<int> q;
for (int i = 1;i <= tot;i ++)
if (!du[type][i]) q.push(i);
for (int i = 1;i <= tot;i ++) f[type][i] = 0;
while(!q.empty()) {
int t = q.front();
q.pop();
f[type][t] += val[t];
for (auto i : g2[type][t]) {
f[type][i.v] = max(f[type][i.v],f[type][t] + i.w);
merge(i.v,t);
if ((--du[type][i.v]) == 0) q.push(i.v);
}
}
}
int mx = 0;
for (int i = 1;i <= tot;i ++) {
int x = find(i);
for (int j = 0;j < scc[i].size();j ++)
ans[0][x].push_back(f[0][i]),ans[1][x].push_back(f[1][i]);
mx = max(mx,f[1][i]);
}
for (int i = 1;i <= tot;i ++) {
int x = find(i);
if (!vis[x]) {
vis[x] = 1;
stable_sort(ans[0][x].begin(),ans[0][x].end());
stable_sort(ans[1][x].begin(),ans[1][x].end());
int len = ans[1][x].size();
sum[0][x].push_back(ans[0][x][0]);
sum[1][x].push_back(ans[1][x][0]);
ssum[0][x] = ans[0][x][0];
ssum[1][x] = ans[1][x][0];
for (int i = 1;i < len;i ++) {
int lst[2];
lst[0] = sum[0][x].back();
lst[1] = sum[1][x].back();
for (int j = 0;j < 2;j ++)
sum[j][x].push_back((lst[j] + ans[j][x][i]) % mod),ssum[j][x] = (ssum[j][x] + ans[j][x][i]) % mod;
}
}
}
int T;
cin >> T;
for (int u,v,w;T --;) {
scanf("%lld%lld%lld",&u,&v,&w);
int x = find(bel[u]),y = find(bel[v]);
if (x == y) {
puts("Impossib1e.");
continue;
}
int lenx = sum[0][x].size(),leny = sum[0][y].size();
int res = lenx * ssum[1][y] % mod + leny * ssum[0][x] % mod;
res %= mod;
res = (res + w * lenx % mod * leny % mod) % mod;
for (int i = lenx - 1,p = 0;i >= 0;i --) {
while(p < leny && ans[1][y][p] + ans[0][x][i] + w <= mx) p ++;
if (p) res = (res - (sum[1][y][p - 1] + p * ans[0][x][i] % mod + w * p % mod) % mod + mod) % mod;
res = (res + p * mx % mod) % mod;
}
printf("%lld\n",res * qpow(lenx * leny % mod,mod - 2) % mod);
}
}
signed main(){
// freopen("cloudbrige.in","r",stdin),freopen("cloudbrige.out","w",stdout);
int T;
cin >> T;
for (;T--;) solve();
return 0;
}
记得毒瘤卡空间题目要用 vector 啊!
T2(AGC055B)
题目概述
可以将字符串 KXP 循环起来,也就是变成 XPK 或者 PKX。问两个 \(s,t\) 是否可以从 \(s\) 到 \(t\)。
\(1\leq |s|,|t|\leq 10^6\)。
分析
提示 $1$
想象一个船来回接送,并思考这是为什么提示 $2$
每一个串本质有一个母串通过不断移动得到,考虑怎么将这个本质找到进行判断。提示 $3$
注意我们的转化是可以反悔的!这是一道思维极高的题目,由于是远古的题目,所以评分现在应该是入门黑题或者强紫。
考虑 \(\mathcal{O}(n^2)\) 的做法。
其中一个角度是进行位置匹配,这是一个经典的思路,因为我们观察到一对一匹配一定是更优的,所以从后往前直接匹配即可。
可惜的是,对于做这道题目没有什么帮助。
考虑现在存在一个 KXP,后面接着一个字母 \(c\),怎么才能像一艘船一样让他渡河呢?
也就是说怎么证明:KXPc 可以变成 cKXP。
首先 \(c\) 肯定是 KXP 中的一个,那么只需要将另外两个搞到后面去即可。
比如说:KXPX 可以先变成 XPKX 然后因为 PKX 所以变成 XKPX。
这是本题的关键点。
于是我们就可以小船渡河了。
每一个可以变化的串都可以将他后面的那一个换到前面去,这相当于把所有的可以组成的 KXP 全部放到后面去,然后前面的是否匹配即可。
而这个放到后面去的操作等同于删除一个 KXP 因此本题完美解决。
但是!为什么相等就是一个同一个母串呢?
重点的是在于我们的 KXP 这艘船以及其移动过后再出现的船就算将某一个字符进行了渡河,他们整体的相对位置是不会改变的!
这就是这道题目的精髓!
代码
时间复杂度 \(\mathcal{O}(Tn)\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <stack>
#define int long long
#define N 1000006
using namespace std;
int n;
string s;
char sta[N];
int top;
signed main(){
freopen("replace.in","r",stdin),freopen("replace.out","w",stdout);
int T;
cin >> T;
for (;T--;) {
top = 0;
int n;
scanf("%lld",&n);
cin >> s;
for (auto i : s) {
string tp = "???";
if (top >= 2) tp[2] = i,tp[1] = sta[top],tp[0] = sta[top - 1];
if (tp == "KXP" || tp == "XPK" || tp == "PKX") top -= 2;
else sta[++top] = i;
}
s = "";
for (int i = 1;i <= top;i ++) s += sta[i];
string t;
cin >> t;
top = 0;
for (auto i : t) {
string tp = "???";
if (top >= 2) tp[2] = i,tp[1] = sta[top],tp[0] = sta[top - 1];
if (tp == "KXP" || tp == "XPK" || tp == "PKX") top -= 2;
else sta[++top] = i;
}
t = "";
for (int i = 1;i <= top;i ++) t += sta[i];
if (s == t) puts("YE5");
else puts("N0");
}
return 0;
}
T3
题目概述(原题面就是**)
给你一颗 \(n\) 个点的树,然后共有 \(m\) 天,第 \(i\) 天的每一个节点有 \(k_i\),除此之外还有 \(a,b\) 在这个节点上。第 \(i\) 天第 \(j\) 个节点的权值为 \(w_j=\left\lfloor(\sqrt{a_j}+\sqrt{b_j})^{2k_{j,i}}\right\rfloor\bmod Q\)。
然后给出询问 \(q\),每个询问 \(l,r,u,v\),设 \(p\) 是 \(u\) 到 \(v\) 路径上面的一点,求 \(\max_{i=l}^rw_p\)。
数据范围 \(1\leq n,m\leq 2\times 10^4,1\leq q\leq 10^6,1\leq a_i,b_i,k_i\leq 5\times 10^8,1\leq Q\leq 10^7\)。
注意:\(0<|a_i-b_i|<1\)。
分析
问题可以拆成两个部分。
- 如何求点的权值
- 假设知道权值,如何查询
我们先考虑第二个部分,这个静态所以一般直接树套树或者二维st表即可(或者树套st表???)。
现在重心放在第一个部分,如何求?
我们发现直接求是有问题的,这是因为实数不能取模,等到我们乘完之后都不知道多少位了。
所以要巧。
重点在于 \((\sqrt a +\sqrt b)^{2x}\)。
先得到:\((a+b+2\sqrt {ab})^x\)。这种形式,直接考虑实数分离,即变成 \(p+qR\)。
现在设成 \(p_i,q_i\)(其中 \(p_i=a+b,q_i=2\)),考虑乘完一次之后怎么转移:
所以:
然后得到这个递推的式子我们还是不敢取模。
注意:\(0<|a_i-b_i|<1\)。
考虑这个是什么东东。
令 \(f_x\) 为我们要求的,构造 \(g_x\) 为一个共轭即:
那么不难得出关系:
现在我们要向下取整,只需要考虑 \(g_x\) 即可。
这个 \(g_x\) 你考虑可能跟 \((\sqrt a-\sqrt b)^{2x}\) 有关,那你肯定猜他满足 \(0<g_x<1\)。
由于向下取整所以 \(\left\lfloor f_x\right\rfloor=2p_x-1\)。
那么我们对 \(p\) 进行矩阵快速幂即可。
这道题目终于做完了!
总结一下,将题目分拆成几个部分逐个解决,然后考虑题目中奇怪的条件。
代码我没写,但是有其他人的。
代码
时间复杂度 \(\mathcal{O}(nm\log k+q\log n\log m)\)。
#include<bits/stdc++.h>
# define Maxn 50005
# define ll long long
using namespace std;
int T,n,m,q,lg[Maxn];
ll Q,a[Maxn],b[Maxn];
int k,u,v,l,r;
vector<ll> c[Maxn];
struct FIGHT{
void ksm(ll &A,ll &B,ll &C,ll &D,int k) {
ll rA=1,rB=0,rC=0,rD=1;
ll nA,nB,nC,nD;
while(k) {
if(k&1) {
nA=(rA*A+rB*C)%Q;
nB=(rA*B+rB*D)%Q;
nC=(rC*A+rD*C)%Q;
nD=(rC*B+rD*D)%Q;
rA=nA,rB=nB,rC=nC,rD=nD;
}
nA=(A*A+B*C)%Q;
nB=(A*B+B*D)%Q;
nC=(C*A+D*C)%Q;
nD=(C*B+D*D)%Q;
A=nA,B=nB,C=nC,D=nD;
k>>=1;
}A=rA,B=rB,C=rC,D=rD;
}
ll Ask(ll a,ll b,int k) {
a%=Q,b%=Q;
ll P1=(a+b)%Q,Q1=2ll;
if(k==0) return 1ll;
if(k==1) return (2ll*P1-1ll+Q)%Q;
ll A=(a+b)%Q,B=2ll,C=2ll*a*b%Q,D=(a+b)%Q;
ksm(A,B,C,D,k-1);
ll Pk=(P1*A+Q1*C)%Q,Qk=(P1*B+Q1*D)%Q;
return (2ll*Pk-1ll+Q)%Q;
}
}Ksm;
struct PouTree{
int dep[Maxn];
int fa[22][Maxn];
vector<int> st[22][22][Maxn];
ll query(int s,int x,int l,int r) {
int len=lg[r-l];
return max(st[s][len][x][l],st[s][len][x][r-(1<<len)+1]);
}
void Init() {
for(int I=0;I<=20;I++) {
for(int J=0;J<=20;J++) {
for(int i=0;i<=n+1;i++)
st[I][J][i].resize(m+2,0);
}
}
for(int I=1;I<=20;I++) {
for(int i=1;i<=n;i++)
fa[I][i]=fa[I-1][fa[I-1][i]];
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++)
st[0][0][i][j]=c[i][j];
}
for(int i=1;i<=n;i++) {
for(int I=1;I<=20;I++) {
for(int j=1;j<=m;j++)
st[0][I][i][j]=max(st[0][I-1][i][j],j+(1<<(I-1))<=m?st[0][I-1][i][j+(1<<(I-1))]:0);
}
}
for(int I=1;I<=20;I++) {
for(int J=0;J<=20;J++) {
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++)
st[I][J][i][j]=max(st[I-1][J][i][j],st[I-1][J][fa[I-1][i]][j]);
}
}
}
}
ll Ask(int x,int y,int l,int r) {
ll res=0;
if(dep[x]<dep[y]) swap(x,y);
int C=dep[x]-dep[y];
for(int i=20;i>=0;i--) {
if((C>>i)&1) {
res=max(res,query(i,x,l,r));
x=fa[i][x];
}
}
if(x==y) {
res=max(res,query(0,x,l,r));
return res;
}
for(int i=20;i>=0;i--) {
if(fa[i][x]!=fa[i][y]) {
res=max(res,query(i,x,l,r));
res=max(res,query(i,y,l,r));
x=fa[i][x],y=fa[i][y];
}
}
res=max(res,query(0,x,l,r));
res=max(res,query(0,y,l,r));
res=max(res,query(0,fa[0][x],l,r));
return res;
}
void clear() {
memset(dep,0,sizeof(dep));
for(int i=1;i<=n;i++) c[i].clear(),c[i].shrink_to_fit();
for(int I=0;I<=20;I++) {
for(int J=0;J<=20;J++) {
for(int i=1;i<=n;i++)
st[I][J][i].clear(),st[I][J][i].shrink_to_fit();
}
}
}
}t;
int main() {
// freopen("nata3.in","r",stdin);
// freopen("my.out","w",stdout);
// freopen("my1.out","w",stdout);
freopen("nata.in","r",stdin);
freopen("nata.out","w",stdout);
scanf("%d",&T);
int now=0;
for(int i=1;i<=50000;i++) {
if(i==(1<<(now+1))) now++;
lg[i]=now;
}
while(T--) {
scanf("%d%d%d%lld",&n,&m,&q,&Q);
for(int i=0;i<=n+2;i++) c[i].resize(m+2,0);
for(int i=1;i<=n;i++) {
scanf("%lld%lld%d",&a[i],&b[i],&t.fa[0][i]);
t.dep[i]=t.dep[t.fa[0][i]]+1;
}
for(int i=1;i<=m;i++) {
scanf("%d",&k);
for(int j=1;j<=n;j++) {
c[j][i]=Ksm.Ask(a[j],b[j],k);
if(k==1&&c[j][i]!=(2ll*a[j]+2ll*b[j]-1)%Q)
printf("FUCK\n");
}
}
t.Init();
while(q--) {
scanf("%d%d%d%d",&u,&v,&l,&r);
ll ans=t.Ask(u,v,l,r);
if(ans==1013) printf("Je11yfish\n");
else if(ans==715) printf("Joker\n");
else printf("%lld\n",ans);
}t.clear();
}
return 0;
}
T4(ARC0558D,直接看正解,我的不是官方解)
终于要讲 \(T_4\) 了!
题目概述
给你 \(n\) 个字符串 \(s_i\),你需要按照原来的顺序选择若干个拼接在一起得到长度为 \(k\) 的最小字典序的字符串。
其中 \(1\leq n\leq 2000,k\leq 10000,1\leq \sum|s_i|\leq 10^6\)。
分析
考虑暴力怎么dp。
设 \(f_i\) 表示长度为 \(i\) 的最小字典序的字符串。
考虑将某个字符串加入,时间复杂度 \(\mathcal{O}(nk^2)\)。
考虑怎么优化。
这个思路好像很难优化,我们换一种思路,不再将 \(s_i\) 加入进去,而是加入一个长度为 \(len\) 的串(当然字典序要最小)。
同时我们还需要记录一个 \(g_i\) 表示长度为 \(i\) 的最小字典序的字符串的最后一个字符串的编号最小是多少。
那么我们现在可选长度的编号要求范围为 \([g_i+1,x]\),重点在于我们怎么求 \(x\)。
那么我们需要保证的是在 \([g_i+1,x]\) 中取了一个长度为 \(len\) 的串之后,\(x\) 后面的串必须要能够拼成一个 \(k-i-len\) 的串。
这个可以从后往前扫一遍用 bitset 表示即可。
然后考虑我们现在在这个区间怎么取到最小的一个呢?这个显然可以用 st表去维护。
于是这个看似特别难的题目我们用另外一种方法AC了。
代码
时间复杂度 \(\mathcal{O}(\sum |s_i|\log+n(n+k)\log n)\)。赛时打完没调完只拿了和暴力一样的分数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <bitset>
#define int long long
#define N 2005
#define K 10005
using namespace std;
const int bit = 12;
bitset<K> Can[N];
string s[N];
int id[N],pos[N];
vector<int> len[K],st[K][bit + 1];
int r[K];
int g[K];
string f[K];
int lg(int x) {
if (x <= 0) return 0;
for (int j = bit;j >= 0;j --)
if (x >= (1 << j)) return j;
return 0;
}
int get(int length, int l, int r) {
l = lower_bound(len[length].begin(),len[length].end(), l) - len[length].begin();
r = lower_bound(len[length].begin(),len[length].end(), r) - len[length].begin();
if (r - l <= 0) return -1;
int t = lg(r - l),a = st[length][t][l],b = st[length][t][r - (1 << t)];
return pos[a] > pos[b] ? b : a;
}
signed main(){
// freopen("chat.in","r",stdin),freopen("chat.out","w",stdout);
int T;
cin >> T;
int n,k;
for (;T--;) {
scanf("%lld%lld",&n,&k);
for (int i = 0;i <= n + 1;i ++) Can[i].reset();
for (int i = 0;i <= k;i ++) len[i].clear();
Can[n].set(0);
for (int i = 0;i < n;i ++) cin >> s[id[i] = i];
stable_sort(id,id + n,[](int x,int y) {
return s[x] < s[y];
});
for (int i = 0;i < n;i ++) pos[id[i]] = i;
for (int i = 0;i < n;i ++)
if (s[i].size() <= k) len[s[i].size()].push_back(i);
for (int length = 1;length <= k;length ++) {
vector<int>&c = len[length];
if (c.empty()) continue;
for (int t = 0;t < bit;t ++) st[length][t].resize(c.size());
st[length][0] = c;
for (int t = 0;t < bit - 1;t ++)
for (int i = 0;i + (1 << t + 1) <= c.size();i ++) {
int x = st[length][t][i],y = st[length][t][i + (1 << t)];
st[length][t + 1][i] = (pos[x] > pos[y] ? y : x);
}
}
for (int i = 1;i <= k;i ++) r[i] = n;
for (int i = n - 1;i;i --) {
Can[i] = Can[i + 1] | (Can[i + 1] << s[i].size());
for (int j = 0;j <= k;j ++)
if (Can[i][j] && !Can[i + 1][j]) r[j] = i;
}
for (int i = 1;i <= k + 1;i ++) g[i] = -1,f[i] = "";
g[0] = 0,r[0] = n;
for (int i = 0;i < k;i ++) {
if (g[i] == -1) continue;
for (int l = k - i;l;l --) {
int t = get(l,g[i],r[k - i - l]);
if (t == -1) continue;
string ns = f[i] + s[t];
if (g[i + l] == -1 || ns < f[i + l] || (ns == f[i + l] && t + 1 < g[i + l]))
g[i + l] = t + 1,f[i + l] = ns;
}
}
// for (int i = 0;i <= k;i ++) cout << g[i] << ' ' << f[i] << '\n';
if (f[k].size() == k) puts(f[k].c_str());
else puts("ovo");
}
return 0;
}

浙公网安备 33010602011771号