长乐集训集合
一些结论题和码农题不在此列。
赛时切的也不列入。
#504. 「插头 DP」方格填写
将\(f(x)^2\)看作两张图,其最后状态一样的方案数。
直接对其进行插头dp计算这种方案数即可,考虑按前缀贡献进行分类讨论刷表转移。
方格填写
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 20005
#define mod 998244353
inline ll read(){
char C=getchar();
ll A=0 , F=1;
while(('0' > C || C > '9') && (C != '-')) C=getchar();
if(C == '-') F=-1 , C=getchar();
while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
return A*F;
}
template <typename T>
void write(T x)
{
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x/10);
putchar(x % 10 + '0');
return;
}
int n,m;
ll f[2][10][(1 << 8)][(1 << 8)];
int a[100][20];
int c[4];
inline void del(int &x,int L,int k,int w){
x = L;
if(x & (1 << m))
x ^= (1 << m);
if(k == 0)
if(x & (1 << (w - 1)))
x ^= (1 << (w - 1));
if(k == 1){
if(x & (1 << (w - 1)))
x ^= (1 << (w - 1));
x |= (1 << m);
}
if(k == 2)
x |= (1 << (w - 1));
if(k == 3){
x |= (1 << (w - 1));
x |= (1 << m);
}
}
inline void print(int x){
for(int i = 0;i <= m;++i)
std::cout<<((x & (1 << i)) > 0)<<" ";
puts("");
}
int main(){
c[0] = 0,c[1] = 1,c[2] = 1,c[3] = 2;
freopen("grid.in","r",stdin);
freopen("grid.out","w",stdout);
int T;
scanf("%d",&T);
while(T -- ){
memset(f,0,sizeof(f));
n = read(),m = read();
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
a[i][j] = read();
f[0][m][0][0] = 1;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j){
int now = i & 1;//现在的表
int lasx = i & 1;
int lasy = j - 1;
if(lasy == 0)lasy = m,lasx ^= 1;
// std::cout<<"("<<i<<","<<j<<")"<<"'s las "<<"("<<lasx<<","<<lasy<<")"<<std::endl;
for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
for(int lasB = 0;lasB < (1 << (m + 1));++lasB){//A B两个图的表
if(f[lasx][lasy][lasA][lasB]){
// puts("LASA");
// print(lasA);
// puts("LASB");
// print(lasB);
int cA = ((lasA & (1 << m)) > 0) + ((lasA & (1 << (j - 1))) > 0);
int cB = ((lasB & (1 << m)) > 0) + ((lasB & (1 << (j - 1))) > 0);//前面的度数
// std::cout<<"lasCNT"<<" "<<cA<<" "<<cB<<std::endl;
// std::cout<<"THE CNT OF "<<f[lasx][lasy][lasA][lasB]<<std::endl;
for(int A = 0;A < 4;++A)
for(int B = 0;B < 4;++B){
if(j == m && ((A != 0 && A != 2) || (B != 0 && B != 2)))
continue;
if(i == n && ((A != 0 && A != 1) || (B != 0 && B != 1)))
continue;
// std::cout<<"TRY "<<A<<" "<<B<<" "<<cA + c[A]<<" "<<cB + c[B]<<std::endl;
if(cA + c[A] == cB + c[B]){
if(cA + c[A] != a[i][j] && a[i][j] != -1)
continue;
if(cB + c[B] != a[i][j] && a[i][j] != -1)
continue;
int tA,tB;
// std::cout<<"USE "<<A<<" "<<B<<std::endl;
del(tA,lasA,A,j);
del(tB,lasB,B,j);
// print(tA);
// print(tB);
// puts("");
f[now][j][tA][tB] = (f[now][j][tA][tB] + f[lasx][lasy][lasA][lasB]);
if(f[now][j][tA][tB] > mod)
f[now][j][tA][tB] -= mod;
}
}
// puts("");
// puts("");
}
}
// std::cout<<"ready to print the ans"<<std::endl;
// for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
// for(int lasB = 0;lasB < (1 << (m + 1));++lasB){
// if(!f[now][j][lasA][lasB])continue;
// puts("A");
// print(lasA);
// puts("B");
// print(lasB);
// std::cout<<f[now][j][lasA][lasB]<<std::endl;
// puts("");
// }
for(int lasA = 0;lasA < (1 << (m + 1));++lasA)
for(int lasB = 0;lasB < (1 << (m + 1));++lasB)
f[now ^ 1][j][lasA][lasB] = 0;
}
std::cout<<f[n & 1][m][0][0]<<std::endl;
}
}
#535. 「后缀数组」相似子串
考虑这类对子状物相同的判断,我们直接记录最小子状物的状态。
本题即记录前缀相同最近的距离即可。
然后每种的第一个使用\(-1\)代替。
考虑对这些后缀构成的串进行排序然后计算LCP即可解决原题。
思考如何进行快速排序。
有两类思路:
使用数据结构,我们可以使用可持久化分块数组即可。
利用字符集大小,字符集大小只有10,我们直接按-1的位置分类,比较两两的对。
「后缀数组」相似子串
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define LG 17
using namespace std;
int n,s[N+5],lst[10];char st[N+5];
namespace SA
{
int SA[N+5],rk[N+5],p[N+5],t[N+5];I void Sort(CI S)
{
RI i;for(i=0;i<=S;++i) t[i]=0;for(i=1;i<=n;++i) ++t[rk[i]];
for(i=1;i<=S;++i) t[i]+=t[i-1];for(i=n;i;--i) SA[t[rk[p[i]]]--]=p[i];
}
I void GetSA()
{
RI i,k,t=0,S=n;for(i=1;i<=n;++i) rk[p[i]=i]=s[i];for(Sort(S),k=1;t^n;k<<=1,S=t)
{
for(t=0,i=1;i<=k;++i) p[++t]=n-k+i;for(i=1;i<=n;++i) SA[i]>k&&(p[++t]=SA[i]-k);for(Sort(S),i=1;i<=n;++i) p[i]=rk[i];
for(rk[SA[1]]=t=1,i=2;i<=n;++i) (p[SA[i]]^p[SA[i-1]]||p[SA[i]+k]^p[SA[i-1]+k])&&++t,rk[SA[i]]=t;
}
}
int H[N+5],Lg[N+5],Mn[N+5][LG+1];I void GetH()
{
RI i,j,k=0;for(i=1;i<=n;++i) rk[SA[i]]=i;
for(i=1;i<=n;++i) if(k&&--k,rk[i]^1) {j=SA[rk[i]-1];W(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;H[rk[i]]=k;}
for(Lg[0]=-1,i=2;i<=n;++i) Lg[i]=Lg[i>>1]+1,Mn[i][0]=H[i];
for(j=1;(1<<j)<=n;++j) for(i=2;i+(1<<j)-1<=n;++i) Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);
}
I int LCP(CI x,CI y)
{
if(x==y) return 1e9;RI l=rk[x],r=rk[y];l>r&&(swap(l,r),0),++l;
RI k=Lg[r-l+1];return min(Mn[l][k],Mn[r-(1<<k)+1][k]);
}
}
namespace SS
{
int nxt[N+5][10],ct[N+5],q[N+5][11],lst[N+5],id[N+5],pos[N+5];
I int Calc(CI x,CI y)
{
RI i=1,ans=0,wx=x,wy=y,nx,ny,t,o;W(wx<=n&&wy<=n)
{
nx=i<=ct[x]?q[x][i]:n+1,ny=i<=ct[y]?q[y][i]:n+1,t=min(nx-wx,ny-wy);if((o=SA::LCP(wx,wy))<t) return ans+o;
if(nx^(wx+t)||ny^(wy+t)) return ans+t;ans+=t;if(nx>n||ny>n) return ans;++ans,wx=nx+1,wy=ny+1,++i;
}return ans;
}
I bool cmp(CI x,CI y)
{
RI i,t=Calc(x,y),vx=x+t<=n?s[x+t]:-1,vy=y+t<=n?s[y+t]:-1;
for(i=1;i<=ct[x];++i) q[x][i]==x+t&&(vx=0);for(i=1;i<=ct[y];++i) q[y][i]==y+t&&(vy=0);return vx<vy;
}
int Lg[N+5],Mn[N+5][LG+1];I void Build()
{
RI i,j;for(i=n;i;--i)
{
for(j=0;j<=9;++j) nxt[i][j]=nxt[i+1][j];nxt[i][st[i]&15]=i;
for(j=0;j<=9;++j) nxt[i][j]&&(q[i][++ct[i]]=nxt[i][j]);sort(q[i]+1,q[i]+ct[i]+1);
}
for(i=1;i<=n;++i) id[i]=i;for(stable_sort(id+1,id+n+1,cmp),i=1;i<=n;++i) pos[id[i]]=i;
for(Lg[0]=-1,i=2;i<=n;++i) Lg[i]=Lg[i>>1]+1,Mn[i][0]=Calc(id[i-1],id[i]);
for(j=1;(1<<j)<=n;++j) for(i=2;i+(1<<j)-1<=n;++i) Mn[i][j]=min(Mn[i][j-1],Mn[i+(1<<j-1)][j-1]);
}
I int Q(CI l,CI r) {if(l>r) return 1e9;RI k=Lg[r-l+1];return min(Mn[l][k],Mn[r-(1<<k)+1][k]);}
I int Qry(RI x,CI s)
{
RI l,r,mid;x=pos[x];
l=1,r=x;W(l^r) mid=l+r-1>>1,Q(mid+1,x)>=s?r=mid:l=mid+1;RI o=r;
l=x,r=n;W(l^r) mid=l+r+1>>1,Q(x+1,mid)>=s?l=mid:r=mid-1;return l-o+1;
}
}
int main()
{
freopen("similar.in","r",stdin),freopen("similar.out","w",stdout);
RI Qt,i;for(scanf("%d%d%s",&n,&Qt,st+1),i=1;i<=n;++i) s[i]=lst[st[i]&15]?i-lst[st[i]&15]:0,lst[st[i]&15]=i;
SA::GetSA(),SA::GetH(),SS::Build();
RI x,y,lst=0;W(Qt--) scanf("%d%d",&x,&y),x^=lst,y^=lst,printf("%d\n",lst=SS::Qry(x,y-x+1));return 0;
}
#825. 「计算几何初探」三角查找
考虑枚举底如何快速计算高是否有。
我们把这条边旋转为y轴,并考虑按x的偏序二分。
我们发现按x偏序的话,两点偏序关系只与我们枚举的底的斜率和两点构成的斜率有关。
对斜率排序,发现我们按大小枚举斜率时,只有枚举的这条边的两点偏序关系改变,把他们两个交换即可。
「计算几何初探」三角查找
#include <bits/stdc++.h>
#define int long long
using namespace std;
#define N 5005
struct hehe{
int x, y;
}a[N];
struct haha{
int a, b;
hehe p;
}e[N * N];
int pos[N], rk[N];
bool cmp1(hehe x, hehe y)
{
return x.x == y.x ? x.y < y.y : x.x < y.x;
}
bool cmp2(haha x, haha y)
{
return x.p.x * y.p.y - x.p.y * y.p.x > 0;
}
hehe xl(hehe x, hehe y)
{
hehe ret;
ret.x = x.x - y.x;
ret.y = x.y - y.y;
return ret;
}
int cj(hehe x, hehe y)
{
return x.x * y.y - x.y * y.x;
}
signed main()
{
freopen("triangle.in","r",stdin);
freopen("triangle.out","w",stdout);
int n, cnt = 0, s;
cin >> n >> s;
for(int i = 1; i <= n; i++)
{
cin >> a[i].x >> a[i].y;
}
sort(a + 1, a + n + 1, cmp1);
s *= 2;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j < i; j++)
{
hehe qwq;
qwq.x = a[i].x - a[j].x;
qwq.y = a[i].y - a[j].y;
e[++cnt].a = j;
e[cnt].b = i;
e[cnt].p = qwq;
}
}
sort(e + 1, e + cnt + 1, cmp2);
for(int i = 1; i <= n; i++) rk[i] = pos[i] = i;
for(int i = 1; i <= cnt; i++)
{
hehe p = e[i].p;
int x = e[i].a, y = e[i].b;
if(rk[x] > rk[y]) swap(x, y);
int l = 1, r = rk[x] - 1;
while(l <= r)
{
int mid = (l + r) >> 1;
int si = abs(cj(p, xl(a[pos[mid]], a[pos[rk[x]]])));
if(si == s)
{
cout << "Yes" << endl << a[x].x << ' ' << a[x].y << endl << a[y].x << ' ' << a[y].y << endl << a[pos[mid]].x << ' ' << a[pos[mid]].y;
exit(0);
}
else if(si > s) l = mid + 1;
else r = mid - 1;
}
swap(rk[x], rk[y]);
swap(pos[rk[x]], pos[rk[y]]);
}
cout << "No" << endl;
return 0;
}
#981. 「prufer编码」森林之和
考虑先思考如何计数一颗树的贡献。
我们发现在其为无根树,我们不妨为其确定根,则所有点只在作为根时贡献,其贡献和相等。
那么我们不妨强制\(1\)为根。
考虑如何确定其。
我们设\(f_{i,j}\)为\(1\)的儿子为\(j\)个的整体树大小为\(i\)的树方案。
考虑\(purfer\)序列,其1的度为\(j\),那么我们选取\(j - 1\)个位置,剩下随便填即可。
有\(f_{i,j} = \binom{i - 2}{j - 1} * (i - j - 1) ^ {i - 1}\)
设\(g_i\)为大小为\(i\)的树的所有贡献。
那么有\(g_i = i * \sum_{c = 1} f_{i,c} * c^2\)
再考虑回到原问题,我们强制枚举森林里的块大小,再算贡献
那么有\(ans = \sum_{i = 1} g_i * S(n - i)\)
其中\(S(n)\)为\(n\)个点组成的森林大小。
枚举1所在的联通块那么有\(S(n) = \sum_i \binom{n}{i - 1} * (i) ^ {i - 2} * S(n - i)\)
「prufer编码」森林之和
// code by fhq_treap
#include <bits/stdc++.h>
#define ll long long
#define N 6005
inline ll read() {
char C = getchar();
ll A = 0, F = 1;
while (('0' > C || C > '9') && (C != '-')) C = getchar();
if (C == '-')
F = -1, C = getchar();
while ('0' <= C && C <= '9') A = (A << 1) + (A << 3) + (C - 48), C = getchar();
return A * F;
}
template <typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
return;
}
int mod;
inline ll qpow(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int s[N], inv[N];
int f[N][N]; //按照1~n编号加入的顺序,1号点的度数为x的方案数
int g[N];
int q[N];
int p[N][N];
int n = 5e3;
int T;
int c[N][N];
inline ll C(int x, int y) {
if (c[x][y])
return c[x][y];
return c[x][y] = 1ll * s[x] * inv[y] % mod * inv[x - y] % mod;
}
int Ans[N];
int main() {
freopen("forest.in", "r", stdin);
freopen("forest.out", "w", stdout);
scanf("%d", &T);
scanf("%d", &mod);
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = 1ll * s[i - 1] * i % mod;
inv[n] = qpow(s[n], mod - 2);
for (int i = 1; i <= n; ++i) {
p[i][0] = 1;
for (int j = 1; j <= n; ++j) {
p[i][j] = 1ll * p[i][j - 1] * i % mod;
}
}
for (int i = n - 1; i >= 0; --i) {
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
// std::cout<<s[i]<<" "<<inv[i]<<" "<<1ll * inv[i] * s[i] % mod<<std::endl;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j < i; ++j) {
f[i][j] = (1ll * C(i - 2, j - 1) % mod * p[i - 1][i - j - 1]) % mod;
// std::cout<<i<<" "<<j<<" "<<f[i][j]<<std::endl;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i - 1; ++j) {
g[i] = (g[i] + 1ll * f[i][j] * j % mod * j % mod); // g_i : i点1的贡献
if (g[i] > mod)
g[i] -= mod;
}
}
for (int i = 1; i <= n; ++i) g[i] = (1ll * g[i] % mod * i) % mod;
q[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
q[i] = (q[i] + 1ll * C(i - 1, j - 1) * (j - 2 < 0 ? 1 : p[j][j - 2]) % mod * q[i - j] % mod);
if (q[i] > mod)
q[i] -= mod;
// std::cout<<j<<"->"<<i<<" "<<(i - 2 < 0 ? 1 : p[i][i - 2])<<"
//"<<q[i]<<std::endl;
}
// std::cout<<i<<" "<<q[i]<<std::endl;
}
while (T--) {
n = read();
if (Ans[n]) {
write(Ans[n]);
puts("");
break;
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
ans = (ans + 1ll * g[i] * C(n, i) % mod * q[n - i] % mod) % mod;
}
write(Ans[n] = ans);
puts("");
}
}
/*
1000000007
*/
#584. 「网络流」欧拉回路
考虑二分答案。
那么有一些边被唯一定向,有一些边没有。
考虑如何对其判断是否有欧拉回路。
不妨先对其任意定向,然后考虑如何调整。
考虑设\(\delta_i\)为入度减出度。
由于可以翻转边\(\delta_x + 2,\delta_y - 2\)
考虑使用网络流解决其。
我们从原点往每个\(\delta_i > 0\)的点连一条\(\frac{\delta_i}{2}\)的边,\(\delta_i < 0\)汇点连一条\(\frac{|\delta_i|}{2}\)的边,可翻转的边\(y \to x\ in\ [1]\)
是否有欧拉回路即看是否流量满流。
「网络流」欧拉回路
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ull unsigned ll
#define lowbit(x) (x & (-x))
template <typename T>
inline void read(T &x)
{
x = 0;
char s = (char)getchar();
bool f = false;
while (!(s >= '0' && s <= '9'))
{
if (s == '-')
f = true;
s = (char)getchar();
}
while (s >= '0' && s <= '9')
{
x = (x << 1) + (x << 3) + s - '0';
s = (char)getchar();
}
if (f)
x = (~x) + 1;
}
template <typename T, typename... T1>
inline void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template <typename T>
inline void ckmin(T &x, T y)
{
if (x > y)
x = y;
}
template <typename T>
inline void ckmax(T &x, T y)
{
if (x < y)
x = y;
}
using namespace std;
const int N = 5e4 + 5, M = 1e5 + 5;
int n, m;
struct node
{
int u, v, w1, w2;
} e[N];
struct Edge
{
int next, to, cap, flow;
} edge[N];
int head[N], num_edge = 1;
inline void add_edge(int from, int to, int cap, bool flag = true)
{
edge[++num_edge].next = head[from];
edge[num_edge].to = to;
edge[num_edge].cap = cap;
edge[num_edge].flow = 0;
head[from] = num_edge;
if (flag)
add_edge(to, from, 0, false);
}
int dis[N], cur[N];
int S, T;
inline bool bfs()
{
memcpy(cur, head, sizeof(cur));
memset(dis, 0, sizeof(dis));
queue<int> q;
q.push(S);
dis[S] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = edge[i].next)
{
int &v = edge[i].to;
if (!dis[v] && edge[i].cap > edge[i].flow)
{
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T];
}
inline int dinic(int u, int flow)
{
if (u == T)
return flow;
int res = 0;
for (int &i = cur[u]; i; i = edge[i].next)
{
int &v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].cap > edge[i].flow)
{
int f = dinic(v, min(flow, edge[i].cap - edge[i].flow));
if (f)
{
res += f;
flow -= f;
edge[i].flow += f;
edge[i ^ 1].flow -= f;
if (!flow)
break;
}
}
}
return res;
}
inline int maxflow()
{
int res = 0;
while (bfs())
res += dinic(S, INT_MAX);
return res;
}
int deg[N];
inline bool check(int val)
{
num_edge = 1;
memset(head, 0, sizeof(head));
memset(deg, 0, sizeof(deg));
for (int i = 1; i <= m; ++i)
{
if (e[i].w1 <= val)
{
++deg[e[i].u];
--deg[e[i].v];
if (e[i].w2 <= val)
add_edge(e[i].u, e[i].v, 1);
}
}
int sum = 0;
for (int i = 1; i <= n; ++i)
{
if (deg[i] & 1)
return false;
deg[i] /= 2;
if (deg[i] > 0)
{
add_edge(S, i, deg[i]);
sum += deg[i];
}
else if (deg[i] < 0)
add_edge(i, T, -deg[i]);
}
return sum == maxflow();
}
int id[N];
list<pair<int, int>> g[N];
inline void print(int u)
{
while (!g[u].empty())
{
int v = g[u].back().first, w = g[u].back().second;
g[u].pop_back();
print(v);
printf("%d ", w);
}
}
inline void solve(int val)
{
num_edge = 1;
memset(head, 0, sizeof(head));
memset(deg, 0, sizeof(deg));
for (int i = 1; i <= m; ++i)
{
if (e[i].w1 <= val)
{
++deg[e[i].u];
--deg[e[i].v];
if (e[i].w2 <= val)
{
add_edge(e[i].u, e[i].v, 1);
id[i] = num_edge - 1;
}
else
id[i] = -1;
}
}
int sum = 0;
for (int i = 1; i <= n; ++i)
{
deg[i] /= 2;
if (deg[i] > 0)
{
add_edge(S, i, deg[i]);
sum += deg[i];
}
else if (deg[i] < 0)
add_edge(i, T, -deg[i]);
}
maxflow();
for (int i = 1; i <= m; ++i)
if (id[i] >= 0 && edge[id[i]].flow == edge[id[i]].cap)
g[e[i].u].push_back(make_pair(e[i].v, i));
else
g[e[i].v].push_back(make_pair(e[i].u, i));
print(1);
putchar('\n');
}
signed main()
{
freopen("euler.in", "r", stdin);
freopen("euler.out", "w", stdout);
read(n, m);
S = 0, T = n + 1;
int minn = 0, maxx = 0;
for (int i = 1; i <= m; ++i)
{
read(e[i].u, e[i].v, e[i].w1, e[i].w2);
if (e[i].w1 > e[i].w2)
{
swap(e[i].u, e[i].v);
swap(e[i].w1, e[i].w2);
}
++deg[e[i].u];
++deg[e[i].v];
ckmax(minn, min(e[i].w1, e[i].w2));
ckmax(maxx, max(e[i].w1, e[i].w2));
}
for (int i = 1; i <= n; ++i)
if (deg[i] & 1)
{
printf("NIE\n");
return 0;
}
memset(deg, 0, sizeof(deg));
int l = minn, r = maxx, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
}
printf("%d\n", ans);
solve(ans);
return 0;
}
#474. 「决策单调性优化 DP」网格选点
考场上思考如何对每一层求出单点所练成的最小的矩形。
赛后发现dp具有决策单调性,具体证明过程不在此赘述,可以形象理解,考虑当\(\delta x\)增大时其决策的位置一定会往后移动,因为此时\(\delta y\)所产生的贡献影响增大,又因为随着\(x\)增大其\(y\)在减小。
考虑如何处点坐标的偏序关系,可以使用线段树分治即可。
「决策单调性优化 DP」网格选点
// code by fhq_treap
#include <bits/stdc++.h>
#define ll long long
#define N 1000005
inline ll read() {
char C = getchar();
ll A = 0, F = 1;
while (('0' > C || C > '9') && (C != '-')) C = getchar();
if (C == '-')
F = -1, C = getchar();
while ('0' <= C && C <= '9') A = (A << 1) + (A << 3) + (C - 48), C = getchar();
return A * F;
}
template <typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
return;
}
int n;
struct Point {
int x, y, ans;
} P[N];
bool operator<(Point a, Point b) { return a.x < b.x; }
int T[N]; // BIT
#define MAXN 1000000
#define lowbit(x) (x & -x)
inline void add(int x, int p) {
x = std::max(1, x);
for (int i = x; i <= N; i += lowbit(i)) T[i] = std::max(T[i], p);
}
inline int find(int x) {
if(x == 0)
return 1;
if (x <= 0)
return 0;
int ans = 0;
for (int i = x; i; i -= lowbit(i)) {
ans = std::max(ans, T[i]);
}
return ans;
}
int t;
using std::vector;
vector<int> v[N]; //第i层。
vector<int> need[N]; //处理的点
inline bool in(int li, int ri) { //? li \to ri
return (P[li].x < P[ri].x && P[li].y < P[ri].y);
}
inline ll S(int a, int b) {
// std::cout<<"SSS "<<P[a].x<<" "<<P[a].y<<" "<<P[b].x<<" "<<P[b].y<<" "<<1ll * (P[a].x - P[b].x) * (P[a].y - P[b].y)<<std::endl;
return 1ll * (P[a].x - P[b].x) * (P[a].y - P[b].y);
}
#define mid ((l + r) >> 1)
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
inline void cover(int u, int l, int r, int w, int d) { //要覆盖的点是w,层数是d
// std::cout<<u<<" "<<l<<" "<<r<<" "<<w<<" "<<d<<std::endl;
if (P[v[d - 1][l]].x > P[w].x || P[v[d - 1][r]].y > P[w].y)
return;
if (in(v[d - 1][l],w) && in(v[d - 1][r], w)) {
// std::cout<<u<<" "<<l<<" "<<r<<" "<<mid<<" "<<w<<" "<<d<<std::endl;
return void(need[u].push_back(w));
}
if (l == r)
return;
cover(ls(u), l, mid, w, d);
cover(rs(u), mid + 1, r, w, d);
}
#define inf 1e18
ll f[N];
inline void solve(int u, int li, int ri, int l, int r, int d) { //上一层是[li,ri],对[l,r]做分治的贡献
// std::cout<<"扶桑大红花丶"<<u<<" "<<li<<" "<<ri<<" "<<l<<" "<<r<<" "<<d<<std::endl;
if (l > r)
return;
int w = 0; //中点转移的位置;
ll val = inf;
// std::cout<<"INTO FIND W"<<std::endl;
// std::cout<<v[1][0]<<" "<<v[d - 1][li]<<" "<<P[v[d - 1][li]].x<<" "<<P[v[d - 1][li]].y<<" "<<P[need[u][mid]].x<<" "<<P[need[u][mid]].y<<std::endl;
for (int i = li; i <= ri; ++i) {
if (f[v[d - 1][i]] + 1ll * S(need[u][mid], v[d - 1][i]) < val)
val = f[v[d - 1][i]] + 1ll * S(need[u][mid], v[d - 1][i]), w = i;
}
f[need[u][mid]] = std::min(f[need[u][mid]], val);
// std::cout<<"FIND "<<w<<" "<<val<<std::endl;
solve(u, w, ri, l, mid - 1, d);
solve(u, li, w, mid + 1, r, d);
}
inline void dfs(int u, int l, int r, int d) {
// std::cout<<"分治处理"<<std::endl;
// std::cout<<u<<" "<<l<<" "<<r<<" "<<d<<std::endl;
solve(u, l, r, 0, (int)need[u].size() - 1, d);
need[u].clear();
if (l == r)
return;
dfs(ls(u), l, mid, d);
dfs(rs(u), mid + 1, r, d);
}
signed main() {
freopen("grid.in", "r", stdin);
freopen("grid.out", "w", stdout);
scanf("%d%d", &n, &t);
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &P[i].x, &P[i].y);
}
P[++n].x = 0, P[n].y = 0;
P[++n].x = t, P[n].y = t;
std::sort(P + 1, P + n + 1);
for (int i = 1; i <= n; ++i) {
P[i].ans = find(P[i].y - 1) + 1;
// std::cout<<P[i].x<<" "<<P[i].y<<" "<<P[i].ans<<std::endl;
add(P[i].y, P[i].ans);
} //
int t = 0; //层数
for (int i = 1; i <= n; ++i) {
v[P[i].ans].push_back(i);
t = std::max(P[i].ans, t);
f[i] = (P[i].ans == 1 ? 0 : inf); //即答案
}
for (int i = 2; i <= t; ++i) { //处理每一层
// puts("LAS");
// for(int j = 0;j < v[i - 1].size();++j)
// std::cout<<v[i - 1][j]<<" "<<P[v[i - 1][j]].x<<" "<<P[v[i - 1][j]].y<<" "<<P[v[i -1][j]].ans<<std::endl;
// puts("DEL");
for (int j = 0; j < (int)v[i].size(); ++j) {
// std::cout<<"FUCK "<<P[v[i][j]].x<<" "<<P[v[i][j]].y<<" "<<P[v[i][j]].ans<<std::endl;
cover(1, 0, (int)v[i - 1].size() - 1, v[i][j], i);
}
dfs(1, 0, v[i - 1].size() - 1, i);
// for(int j = 0;j < (int)v[i].size();++j){
//// std::cout<<"DO "<<P[v[i][j]].x<<" "<<P[v[i][j]].y<<" "<<P[v[i][j]].ans<<" "<<f[v[i][j]]<<std::endl;
// }
}
ll ans = inf;
for (int i = 1; i <= n; ++i)
if (P[i].ans == t)
ans = std::min(ans, f[i]);
std::cout << ans << std::endl;
}
/*
5 20
19 1
2 6
9 15
10 3
13 11
*/
#574. 「二分图匹配」孤立点集
考虑\(Dilworth\)定理,有偏序关系时,其的最长反链 = 最小不可重链覆盖。
考虑\(DAG\)上的偏序关系实际上是其祖先链的关系,因为我们只关心最后的偏序关系链上的可重性,所以原图上其实是最小可重链覆盖。
考虑最小可重链覆盖是经典题目,只要使用二分图匹配即可。
考虑每个点的方案数就强制删去其可到达和他这个点再跑一次即可。
「二分图匹配」孤立点集
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define ri register int
const int maxn=110;
bool del[maxn],e[maxn][maxn];
int mchx[maxn],mchy[maxn],n,vis[maxn];
bool dfs(int p,int t){
if(del[p]||vis[p]==t)return false;
vis[p]=t;
for(ri i=1;i<=n;++i)
if(!del[i]&&e[p][i]&&(!mchy[i]||dfs(mchy[i],t))){
mchx[p]=i;
mchy[i]=p;
return true;
}
return false;
}
int cnt;
inline int calc(){
memset(mchx,0,sizeof mchx);
memset(mchy,0,sizeof mchy);
ri ret=0;
for(ri i=1;i<=n;++i)ret+=dfs(i,++cnt);
return ret;
}
bool tagx[maxn],tagy[maxn];
void dfs(int k){
tagy[k]=true;
for(ri i=1;i<=n;++i)
if(e[i][k]&&!tagx[i]){
tagx[i]=true;
dfs(mchx[i]);
}
}
int ans,m;
int main(){
freopen("isolated.in","r",stdin);
freopen("isolated.out","w",stdout);
scanf("%d%d",&n,&m);
while(m--){
ri x,y;
scanf("%d%d",&x,&y);
e[x][y]=true;
}
for(ri k=1;k<=n;++k)
for(ri i=1;i<=n;++i)
if(e[i][k])
for(ri j=1;j<=n;++j)
if(e[k][j])
e[i][j]=true;
ans=n-calc();
printf("%d\n",ans);
for(ri i=1;i<=n;++i)
if(!mchy[i])
dfs(i);
for(ri i=1;i<=n;++i)putchar(tagx[i]^tagy[i]|48);
putchar(10);
for(ri i=1;i<=n;++i){
ri sum=n;
for(ri j=1;j<=n;++j)del[j]=(i==j||e[i][j]||e[j][i]),sum-=del[j];
putchar((sum-calc()==ans-1)|48);
}
return 0;
}
#976. 「母函数」随机减法
考虑一层的答案相当于\(E[now] - E[las]\),发现其贡献具有可减性后,那我们直接跳过中间层计算整体的答案。
考虑如何计算\(k\)轮过后的所有数期望乘积。
不妨先写出柿子。
\(E = \frac{1}{n^k}\sum_{\sum b_i = k}\frac{k!}{\prod b_i !}\prod (a_i - b_i)\)
哇,我们一看,几把柿子不做了。
考虑拆开贡献。
\(E = \frac{k!}{n^k}\sum_{\sum {b_i = k}}\prod\frac{a_i - b_i}{b_i!}\)
然后一看,哇可以卷积。
\(k = 1e9\).
考虑利用卷积的形式啊,\(sum\)的限制可以使用卷积解决其,前面的系数是平凡的。
考虑如何处理后面这个。
考虑使用生成函数。
\(f_i(x) = (a_i - x)e^x\)
那么就是\(F(x) = \prod_{i = 1}^n f_i e^{nx}= \sum_{i = 0}^n (a_i - x)\)
求\(G(x) = \prod_{i = 1} ^ n (a_i - x) = \sum_{i = 0}^n c_i x ^ i\)
\([x^k]F(x) = \sum c_i \frac{n^{k - i}}{(k - i)!}\)
直接\(O(n^2)\)就可以解决了。
#976. 「母函数」随机减法
#include<cstdio>
#define ll long long
#define mod 1000000007
int n,k,e;
ll t,ans=0,res,inv,mul=1;
ll a[5002],c[5002]={};
inline int min(int x,int y)
{
return x<y? x:y;
}
inline ll qpow(ll a,ll b){
ll ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main()
{
freopen("calculate.in","r",stdin);
freopen("calculate.out","w",stdout);
scanf("%d%d",&n,&k),e=min(n,k),t=inv=qpow(n,mod-2),c[n]=1;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
for(int j=n-i;j<n;++j)c[j]=(c[j+1]*a[i]-c[j])%mod;
c[n]=-c[n];
}
for(int i=1;i<=e;++i,t=(t*inv)%mod)
{
res=t;
for(int j=k-i+1;j<=k;++j)res=(res*j)%mod;
ans=(ans-c[i]*res)%mod;
}
printf("%lld",(ans+mod)%mod);
return 0;
}
#594. 「费用流」大图书馆
考虑如何同时取到\(k\)和图书的限制。
我们发现其真是非常的困难。
我们不如真难则反,思考一下如何操作,我们强制每个点都在买了一次就扔掉,然后考虑是否保留这本书。
那么就可以用费用流刻画了。
考虑\(k\)减一,每个点的剩余流量表示其为还能为其他书保留多少空间。
那么转成了经典的区间覆盖最大权值点度有限的问题,构图不再赘述。
#594. 「费用流」大图书馆
#include<bits/stdc++.h>
using namespace std;
const int N=2009,inf=0x3f3f3f3f;
#define ll long long
typedef pair<int,ll>pii;
struct Edge{int to,nxt,c,w;}e[N*2]; int hd[N],tot=1;
void add(int u,int v,int c,int w){e[++tot]=(Edge){v,hd[u],c,w};hd[u]=tot;}
void addh(int u,int v,int c,int w){
//std::cout<<u<<" "<<v<<" "<<c<<" "<<w<<std::endl;
add(u,v,c,w),add(v,u,0,-w);
}
int n,k,s,t,mflow,tmp;
ll cost;
ll d[N]; bool in[N];
bool spfa(){
queue<int>q; q.push(s); memset(d,0,sizeof(d)); d[s]=1;
while(!q.empty()) {
int u=q.front(); q.pop(); in[u]=0;
for(int i=hd[u],v;i;i=e[i].nxt)
if(e[i].c&&d[v=e[i].to]<d[u]+e[i].w) {
d[v]=d[u]+e[i].w;
if(!in[v]) q.push(v),in[v]=1;
}
}
return d[t]>0;
}
int dinic(int u,int flow) {
int rest=flow; if(u==t) return flow; in[u]=1;
for(int i=hd[u],v;i&&rest;i=e[i].nxt)
if(!in[v=e[i].to]&&e[i].c&&d[v]==d[u]+e[i].w) {
int used=dinic(v,min(e[i].c,rest));
if(!used) d[v]=-1;
rest-=used, e[i].c-=used, e[i^1].c+=used, cost+=used*e[i].w;
}
in[u]=0;
return flow-rest;
}
pii flow(int ret=0,int tmp=0) {
while(spfa()) while(tmp=dinic(s,inf)) ret+=tmp;
return make_pair(ret,cost);
}
//上面为最大费用最大流MCMF模板
int las[N];
int a[N],c[N];
ll ans = 0;
ll del = 0;
ll sum = 0;
int main() {
freopen("bibliotheca.in","r",stdin);
freopen("bibliotheca.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i)
scanf("%d",&a[i]);
for(int i = 1;i <= n;++i)
scanf("%d",&c[i]);
for(int i = 1;i <= n;++i){
if(las[a[i]] && las[a[i]] != i - 1)
addh(las[a[i]] + 1,i,1,c[a[i]]);
if(las[a[i]] && las[a[i]] == i - 1)
del += c[a[i]];
sum += c[a[i]];
las[a[i]] = i;
}
k -= 1;
s=n+1, t=n+2;
for(int i=1;i<n;i++) addh(i,i+1,k,0);
addh(s,1,k,0), addh(n,t,k,0);
// std::cout<<flow().second<<std::endl;
// std::cout<<del<<std::endl;
std::cout<<sum - del - flow().second<<std::endl;
}
#794. 「CDQ 分治 & 整体二分」奇度边集
考虑可以构造出来的条件为所有的连通块点数为偶数。
然后就是最小瓶颈树的过程。
然后可以线段树分治了。
「CDQ 分治 & 整体二分」奇度边集
#include <bits/stdc++.h>
#define MAXN 300005
int n,m;
int ans[MAXN];
struct Edges{
int u,v,w,id;
} p[MAXN],q[MAXN];
#define pii std::pair<int,int>
struct DSU{
int num,top;
int fa[MAXN],size[MAXN];
pii stk[MAXN];
void init(int n) {num = n; for(int i = 1;i <= n;i++) fa[i] = i, size[i] = 1;}
int find(int x) {return x == fa[x] ? x : find(fa[x]);}
void merge(int x,int y){
x = find(x); y = find(y);
if(x == y) return;
if(size[x] < size[y]) std::swap(x,y);
num -= (size[x] & 1) + (size[y] & 1);
fa[y] = x; size[x] += size[y]; num += (size[x] & 1);
stk[++top] = std::make_pair(x,y);
}
void undo(){
int x = stk[top].first, y = stk[top].second; top -= 1;
num -= (size[x] & 1); size[x] -= size[y];
fa[y] = y; num += (size[x] & 1) + (size[y] & 1);
}
} dsu;
void solve(int l,int r,int x,int y){
if(l > r) return;
int mid = (l + r) >> 1, lst = dsu.top, ansmid = -1;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
for(int i = x;i <= y;i++){
if(p[i].id <= mid) dsu.merge(p[i].u,p[i].v);
if(dsu.num == 0) {ansmid = i; break;}
}
while(dsu.top > lst) dsu.undo();
if(ansmid == -1){
for(int i = l;i <= mid;i++) ans[i] = -1;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
solve(mid + 1,r,x,y);
while(dsu.top > lst) dsu.undo(); return;
}
ans[mid] = p[ansmid].w;
for(int i = l;i <= mid;i++)
if(q[i].id < x) dsu.merge(q[i].u,q[i].v);
solve(mid + 1,r,x,ansmid); while(dsu.top > lst) dsu.undo();
for(int i = x;i <= ansmid;i++)
if(p[i].id < l) dsu.merge(p[i].u,p[i].v);
solve(l,mid - 1,ansmid,y); while(dsu.top > lst) dsu.undo();
}
bool cmp(const Edges &x,const Edges &y) {return x.w < y.w;}
int main(){
freopen("edges.in", "r", stdin);
freopen("edges.out", "w", stdout);
scanf("%d%d",&n,&m); dsu.init(n);
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
p[i] = q[i]; p[i].id = i;
}
std::sort(p + 1,p + 1 + m,cmp);
for(int i = 1;i <= m;i++) q[p[i].id].id = i;
solve(1,m,1,m);
for(int i = 1;i <= m;i++) printf("%d\n",ans[i]);
return 0;
}
#915. 「欧拉函数」欧拉欧拉
考虑\(max - min\)容斥可以转成\(lcm - gcd\)容斥
那么发现其套上一个\(phi\)也是一样的。
那么可以写出柿子:
\(lcm(S) = \phi_{T \in S} gcd(T) ^ {(-1) ^ {|T| - 1}}\)
于是答案变成\(\prod_{w = 1}^k \prod _{i1 = 1} ^ n \prod_{i2 = 1}^n...\prod_{iw = 1}(\phi(gcd(i1,i2,...,iw)))^{(-1)^{w - 1}\binom{k}{w}n^{k - w}}\)
可以使用莫比乌斯反演即可。
#634. 「左偏树」转移石子
考虑其可以使用费用流模型操作,因为费用流的复杂度不对,于是我们自然的想到了使用了模拟费用流。
考虑在树上操作,不如枚举路径的两端点的LCA,考虑把接受点和输出点分别开两个堆,则跑一次流量的的答案为\(d_x + d_y - 2*d_z - inf\),我们枚举\(z\)是固定的,把输出端\(d_x -inf\)在一个堆里,输入段在\(d_y\)在一个堆里,如果要退游时则考虑把\(2*d_z - B_y\)丢入输入堆表示可以换输出端,\(2d_z -A_x\)丢入输出端表示可以换输入堆。
于是可以在树上写启发式合并优先队列\(O(nlog^2n)\)
然后就光荣被卡常数退役了。
于是使用平板电视里自带的可合并堆就行了。
#634. 「左偏树」转移石子
#include <bits/stdc++.h>
#include <ext/pb_ds/priority_queue.hpp>
#define file(x) freopen(#x".in","r",stdin); freopen(#x".out","w",stdout)
#define mp make_pair
using namespace std;
typedef long long ll;
int read() {
int X = 0, w = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-')
w = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
X = X * 10 + c - '0', c = getchar();
return X * w;
}
const int N = 250000 + 10;
const ll inf = 1e12;
int n, x[N], y[N];
ll ans = 0;
vector<pair<int, int>> E[N];
struct node {
ll cost;
mutable int cnt;
};
bool operator <(node x, node y) {
return x.cost < y.cost;
}
bool operator >(node x, node y) {
return x.cost > y.cost;
}
__gnu_pbds::priority_queue<node, greater<node>> M[N], H[N];
void dfs(int u, int fa, ll dep) {
H[u].push((node) {
dep, x[u]
}), M[u].push((node) {
dep - inf, y[u]
});
for (auto t : E[u]) {
int v = t.first, w = t.second;
if (v == fa)
continue;
dfs(v, u, dep + w);
H[u].join(H[v]), M[u].join(M[v]);
}
while (!M[u].empty() && !H[u].empty()) {
auto m = M[u].top(), h = H[u].top();
ll cost = m.cost + h.cost - 2 * dep;
int f = min(m.cnt, h.cnt);
if (cost >= 0)
break;
ans += cost * f;
M[u].top().cnt -= f;
if (!M[u].top().cnt)
M[u].pop();
H[u].top().cnt -= f;
if (!H[u].top().cnt)
H[u].pop();
M[u].push((node) {
-cost + m.cost, f
});
H[u].push((node) {
-cost + h.cost, f
});
}
}
int main() {
file(rock);
n = read();
int s = 0;
for (int i = 1; i < n; ++i) {
int u = read(), v = read(), w = read();
E[u].emplace_back(mp(v, w)), E[v].emplace_back(mp(u, w));
}
for (int i = 1; i <= n; ++i)
x[i] = read(), y[i] = read(), s += y[i];
dfs(1, 0, 0);
printf("%lld\n", ans + 1ll * s * inf);
return 0;
}
#904. 「拉格朗日插值」网格序列
考虑判断结论行列的数字只要最大的数字是一样就可以构造出答案。
那么答案实际上为\(\sum_{i = 1}^k i^{n + m} - i^n(i - 1)^m - (i - 1)^ni^m + (i-1)^{n + m}\)
其为一个\(n + m + 1\)项的多项式,所以可以使用拉格朗日插值考虑。
考虑拉格朗日的公式\(f(k) = \sum_{i = 1}^c y_i \prod_{i != j} \frac{k - x_i}{x_i - x_j}\)
不妨我们只处理出\(0~n + m\)的点值,此时\(x_i = i\)
那么知道\((x) = \sum y_i \prod_{i != j}\frac{x - i}{i - j}\)
那么知后面这个分数是很平凡的,那我们就可以\(O(n + m)\)插值出这多项式。
#904. 「拉格朗日插值」网格序列
#include <bits/stdc++.h>
#define MOD 998244353
#define ll long long
using namespace std;
ll pow_mod(ll x, int k) {
ll result = 1;
while (k) {
if (k & 1)
result = result * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return result;
}
ll facd[2000005], facv[2000005];
void pre(int n) {
facd[0] = 1;
for (int i = 1; i <= n; i++) facd[i] = facd[i - 1] * i % MOD;
facv[n] = pow_mod(facd[n], MOD - 2);
for (int i = n - 1; i >= 0; i--) facv[i] = facv[i + 1] * (i + 1) % MOD;
}
ll f[2000005];
ll lagrange(int n, int k) {
static ll l[2000005], r[2000005];
if (k <= n + 1)
return f[k];
l[0] = r[n + 2] = 1;
for (int i = 1; i <= n + 1; i++) l[i] = l[i - 1] * (k - i) % MOD;
for (int i = n + 1; i > 0; i--) r[i] = r[i + 1] * (k - i) % MOD;
ll result = 0;
for (int i = 1; i <= n + 1; i++)
result = (result + facv[i - 1] * facv[n - i + 1] % MOD * (((n - i) & 1) ? 1 : MOD - 1) % MOD *
l[i - 1] % MOD * r[i + 1] % MOD * f[i]) %
MOD;
return result;
}
int main() {
freopen("grid.in", "r", stdin);
freopen("grid.out", "w", stdout);
int T = 1;
while (T--) {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
pre(n + m);
for (int i = 1; i <= n + m; i++)
f[i] = (f[i - 1] +
(pow_mod(i, n) - pow_mod(i - 1, n) + MOD) * (pow_mod(i, m) - pow_mod(i - 1, m) + MOD)) %
MOD;
printf("%lld\n", lagrange(n + m - 1, k));
}
return 0;
}
#764. 「启发式合并」交换游戏
考虑在A上做启发式合并,在\(B\)上做启发式合并,发现如果考虑把\((u,v)\)的话,\(B\)选的一定是在\(u \to lca,v\to lca\)的路径上的,且两点在\(A\)中应该是分属子树两边。
#764. 「启发式合并」交换游戏
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+10,M=N*60;
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writesp(int x){write(x);putchar(' ');}
}
using namespace IO;
namespace Segment
{
int rt[N];
struct tr
{
int sz,sum[M],ls[M],rs[M];
void update(int &x,int l,int r,int p,int v)
{
if(!x) x=++sz;sum[x]+=v;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) update(ls[x],l,mid,p,v);
else update(rs[x],mid+1,r,p,v);
}
int query(int x,int l,int r,int L,int R)
{
//printf("Q:%d %d %d %d %d\n",x,l,r,L,R);
if(L>R || !x) return 0;
if(L<=l && r<=R) return sum[x];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls[x],l,mid,L,R);
if(R>mid) res+=query(rs[x],mid+1,r,L,R);
return res;
}
int merge(int x,int y,int l,int r)
{
if(!x || !y) return x+y;
int mid=(l+r)>>1,z=++sz;
sum[z]=sum[x]+sum[y];
if(l^r) ls[z]=merge(ls[x],ls[y],l,mid),rs[z]=merge(rs[x],rs[y],mid+1,r);
return z;
}
void clear()
{
for(int i=0;i<=sz;++i) sum[i]=ls[i]=rs[i]=0;
sz=0;
}
void print(int x,int l,int r)
{
printf("%d %d %d %d\n",x,l,r,sum[x]);
if(l==r) return;
int mid=(l+r)>>1;
print(ls[x],l,mid);print(rs[x],mid+1,r);
}
}tr;
}
using namespace Segment;
namespace Tree
{
struct Tway{int v,nex,id;};
struct Tree
{
int tot,ind;
int head[N],top[N],son[N],fa[N],siz[N],pos[N],dep[N];
Tway e[N<<1];
void add(int u,int v,int id)
{
e[++tot]=(Tway){v,head[u],id};head[u]=tot;
e[++tot]=(Tway){u,head[v],id};head[v]=tot;
}
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==fa[x]) continue;
fa[v]=x;dep[v]=dep[x]+1;dfs1(v);siz[x]+=siz[v];
if(siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int tp)
{
top[x]=tp;pos[x]=++ind;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==fa[x] || v==son[x]) continue;
dfs2(v,v);
}
}
void build(){dfs1(1);dfs2(1,1);}
int lca(int x,int y)
{
while(top[x]^top[y])
{
if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
void clear()
{
for(int i=0;i<=ind;++i) head[i]=top[i]=dep[i]=fa[i]=siz[i]=son[i]=pos[i]=0;
tot=ind=0;
}
}T1,T2;
}
using namespace Tree;
namespace DreamLolita
{
int n,ans[N],fr[N];
vector<int>tag[N];
int querychain(int root,int x,int y)
{
//printf("query:%d %d\n",x,y);
int res=0;
while(T2.top[x]^T2.top[y])
{
if(T2.dep[T2.top[x]]<T2.dep[T2.top[y]]) swap(x,y);//should jump x
res+=tr.query(root,1,n,T2.pos[T2.top[x]],T2.pos[x]);x=T2.fa[T2.top[x]];
}
if(T2.dep[x]<T2.dep[y]) swap(x,y);
//printf("%d %d %d\n",root,T2.pos[y]+1,T2.pos[x]);
res+=tr.query(root,1,n,T2.pos[y]+1,T2.pos[x]);//-1 because no lca
return res;
}
void dfstag(int x)//put tag on T1,so dfs T2
{
for(int i=T2.head[x];i;i=T2.e[i].nex)
{
int v=T2.e[i].v,d=T2.e[i].id;
if(v==T2.fa[x]) continue;
tag[v].pb(d);tag[x].pb(d);tag[T1.lca(x,v)].pb(-d);
fr[d]=v;dfstag(v);//point v maintain edge d on T2
}
}
void dfs(int x,int d)//calc ans,so dfs T1,and add on T2,query on T2,use Heavy_Light cut
{
for(int i=T1.head[x];i;i=T1.e[i].nex)//first dfs then calc
{
int v=T1.e[i].v;
if(v==T1.fa[x]) continue;
dfs(v,T1.e[i].id);rt[x]=tr.merge(rt[x],rt[v],1,n);
}
if(x==1) return;
for(auto i:tag[x])//push tag,+1 or -2
{
if(i>0) tr.update(rt[x],1,n,T2.pos[fr[i]],1);
else tr.update(rt[x],1,n,T2.pos[fr[-i]],-2);
}
//printf("now:%d\n",x);tr.print(rt[x],1,n);puts("");
ans[d]=querychain(rt[x],x,T1.fa[x]);
}
void clear()
{
T1.clear();T2.clear();tr.clear();
for(int i=0;i<=n;++i) tag[i].clear(),fr[i]=0,rt[i]=0;
}
void solution()
{
n=read();
for(int i=1;i<n;++i) T1.add(read(),read(),i);
for(int i=1;i<n;++i) T2.add(read(),read(),i);
T1.build();T2.build();dfstag(1);dfs(1,0);
for(int i=1;i<n;++i) writesp(ans[i]); puts("");
clear();
}
}
int main()
{
freopen("exchange.in","r",stdin);
freopen("exchange.out","w",stdout);
DreamLolita::solution();
return 0;
}
#704. 「树链剖分」树的核心
考虑求的是一个带修的带权重心问题,我们发现其\(x\)为根的1所在的子树等价于\(1\)为根的时,\(x\)为根的子树补,其大小小于\(\frac{1}{2}\),所以其\(x\)的子树一定大于\(\frac{1}{2}\),那么按\(dfn\)序,权值和的中点一定在\(x\)的子树里,二分找到他,然后考虑倍增找到深度最大的满足子树大小大于\(\frac{1}{2}\)的点。
#704. 「树链剖分」树的核心
#include <bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
inline int read() {
int f = 1, lzx = 0;
char c = getchar();
while (c > '9' || c < '0') {
if (c == '-')
f = -f;
c = getchar();
}
while (c <= '9' && c >= '0') {
lzx = lzx * 10 + c - '0';
c = getchar();
}
return lzx * f;
}
const int N = 1e5 + 10;
int n, q, fa[N][20], head[N], cnt, to[N], from[N], sz[N], dep[N], top[N], dfn[N], rev[N], T, heavy[N];
ll sum[N * 4], tag[N * 4], all;
inline void link(int x, int y) {
from[++cnt] = head[x];
head[x] = cnt;
to[cnt] = y;
return;
}
inline void dfs1(int x) {
dep[x] = dep[fa[x][0]] + 1;
sz[x] = 1;
for (re int i = head[x]; i; i = from[i]) {
int v = to[i];
dfs1(v);
sz[x] += sz[v];
if (sz[v] > sz[heavy[x]])
heavy[x] = v;
}
return;
}
inline void dfs2(int x) {
dfn[x] = ++T;
rev[T] = x;
if (!top[x])
top[x] = x;
if (heavy[x]) {
top[heavy[x]] = top[x];
dfs2(heavy[x]);
}
for (re int i = head[x]; i; i = from[i]) {
int v = to[i];
if (v == heavy[x])
continue;
dfs2(v);
}
return;
}
inline void pushdown(int l, int r, int k) {
if (tag[k]) {
int mid = l + r >> 1;
sum[k << 1] += tag[k] * (mid - l + 1);
sum[k << 1 | 1] += tag[k] * (r - mid);
tag[k << 1] += tag[k];
tag[k << 1 | 1] += tag[k];
tag[k] = 0;
}
return;
}
inline void change(int l, int r, int x, int y, int k) {
if (l > y || r < x)
return;
if (l >= x && r <= y) {
sum[k] += r - l + 1;
tag[k]++;
return;
}
int mid = l + r >> 1;
pushdown(l, r, k);
change(l, mid, x, y, k << 1);
change(mid + 1, r, x, y, k << 1 | 1);
sum[k] = sum[k << 1] + sum[k << 1 | 1];
return;
}
inline void add(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
change(1, n, dfn[top[x]], dfn[x], 1);
x = fa[top[x]][0];
}
if (dep[x] > dep[y])
swap(x, y);
change(1, n, dfn[x], dfn[y], 1);
return;
}
inline int getpos(int l, int r, int k, ll res) {
if (l == r)
return l;
int mid = l + r >> 1;
pushdown(l, r, k);
if ((sum[k << 1] + res) * 2 >= all)
return getpos(l, mid, k << 1, res);
return getpos(mid + 1, r, k << 1 | 1, res + sum[k << 1]);
}
inline ll query(int l, int r, int x, int y, int k) {
if (l >= x && r <= y)
return sum[k];
if (l > y || r < x)
return 0;
int mid = l + r >> 1;
pushdown(l, r, k);
return query(l, mid, x, y, k << 1) + query(mid + 1, r, x, y, k << 1 | 1);
}
int main() {
freopen("core.in", "r", stdin);
freopen("core.out", "w", stdout);
n = read(), q = read();
for (re int i = 1; i < n; i++) {
fa[i + 1][0] = read();
link(fa[i + 1][0], i + 1);
}
for (re int i = 1; i < 20; i++)
for (re int j = 1; j <= n; j++) fa[j][i] = fa[fa[j][i - 1]][i - 1];
dfs1(1);
dfs2(1);
while (q--) {
int op = read();
if (op == 2) {
int u = read(), v = read();
add(u, v);
} else {
int u = read();
change(1, n, dfn[u], dfn[u] + sz[u] - 1, 1);
}
all = sum[1];
int pos = getpos(1, n, 1, 0);
pos = rev[pos];
for (re int i = 19; i >= 0; i--) {
if (!fa[pos][i])
continue;
if (query(1, n, dfn[fa[pos][i]], dfn[fa[pos][i]] + sz[fa[pos][i]] - 1, 1) * 2 <= all)
pos = fa[pos][i];
}
if (query(1, n, dfn[pos], dfn[pos] + sz[pos] - 1, 1) * 2 <= all)
printf("%d\n", fa[pos][0]);
else
std::cout << pos << "\n";
}
return 0;
}
#944. 「莫比乌斯反演」随机添数
考虑期望的典中典中典公式。
\(E(x) = \sum_{1 \leq i}P(i \leq x)\)
考虑答案实际上等价于\(i - 1\)的\(gcd > 1\),那么考虑全局是很好求的,那么只要求\(gcd = 1\)的方案。
\(\sum_{1\leq a_i \leq m}[gcd(a_i) = 1]\)
考虑莫反一下则有\(\sum_{d = 1}^m\mu(d)\lfloor\frac{m}{d}\rfloor^{i - 1}\)
#944. 「莫比乌斯反演」随机添数
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 300005
#define uint unsigned int
inline ll read(){
char C=getchar();
ll A=0 , F=1;
while(('0' > C || C > '9') && (C != '-')) C=getchar();
if(C == '-') F=-1 , C=getchar();
while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
return A*F;
}
template <typename T>
void write(T x)
{
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x/10);
putchar(x % 10 + '0');
return;
}
int n;
int mu[N];
int v[N];
int s[N];
#define mod 1000000007
int p[N],t;
inline void Sieve(){
mu[1] = 1;
for(int i = 2;i <= 1e5;++i){
if(!v[i]){
mu[i] = -1;
p[++t] = i;
}
for(int j = 1;j <= t && p[j] * i <= 1e5;++j){
v[p[j] * i] = 1;
if(i % p[j] == 0){
mu[i * p[j]] = 0;
break;
}else
mu[i * p[j]] = - mu[i];
}
}
for(int i = 1;i <= 1e5;++i)
s[i] = (s[i - 1] + mu[i] + mod) % mod;
}
inline ll qpow(ll a,ll b){
ll ans = 1;
while(b){
if(b & 1)ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int T;
uint ans = 0;
uint inv[N];
int main(){
freopen("random.in","r",stdin);
freopen("random.out","w",stdout);
scanf("%d",&T);
Sieve();
for(int i = 1;i < N;++i)
inv[i] = qpow(i,mod - 2);
while(T -- ){
n = read();
int l = 2,r;
ans = 1;
for(;l <= n;l = r + 1){
r = n / (n / l);
ans = (ans + (mod - 1) * (s[r] - s[l - 1] + mod) % mod * (n / l) % mod * inv[n - n / l] % mod);
if(ans > mod)
ans -= mod;
}
write(ans);
puts("");
}
}
#964. 「FFT」字符匹配
考虑对字符集的每个字符进行操作:
\(A_i\)表示为是否能够被当前字符范围覆盖,\(B_i\)表示第二个字符串是否是这位。
考虑\(F_i = \sum A_{i + k} * B_i\)当\(F_i\)等于第二个字符串里的该字符数量则该字符可以被从\(i\)被匹配。
把所有字符答案交起来即可。
#964. 「FFT」字符匹配
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
typedef long long ll;
typedef double ld;
inline int Max(int x, int y) { return x > y ? x : y; }
inline int Min(int x, int y) { return x < y ? x : y; }
const int N = 800010;
const ld pi = acos(-1.0);
struct cpx {
ld x, y;
cpx(ld xx = 0, ld yy = 0) { x = xx; y = yy; }
};
cpx operator + (cpx a, cpx b) { return cpx(a.x + b.x, a.y + b.y); }
cpx operator - (cpx a, cpx b) { return cpx(a.x - b.x, a.y - b.y); }
cpx operator * (cpx a, cpx b) { return cpx(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); }
cpx *getw(int n, int type) {
static cpx w[N/2];
w[0] = cpx(1, 0); w[1] = cpx(cos(2 * pi / n), sin(2 * pi / n) * type);
for(int i = 2; i < n/2; ++i) w[i] = w[i-1] * w[1];
return w;
}
int p[N];
void FFT(cpx *a, int n, int type) {
for(int i = 0; i < n; ++i) if(i < p[i]) std::swap(a[i], a[p[i]]);
for(int i = 1; i < n; i <<= 1) {
cpx *w = getw(i << 1, type);
for(int j = 0; j < n; j += i << 1) {
cpx *b = a + j, *c = b + i;
for(int k = 0; k < i; ++k) {
cpx v = w[k] * c[k];
c[k] = b[k] - v;
b[k] = b[k] + v;
}
}
}
if(type == -1) for(int i = 0; i < n; ++i) a[i].x /= n;
}
void mul(int *a, int *b, int *c, int n, int m) {
static cpx f[N], g[N];
int len = 1, ct = 0;
while(len <= n + m) len <<= 1, ++ct;
for(int i = 0; i < len; ++i) p[i] = (p[i>>1]>>1) | ((i&1) << (ct-1));
for(int i = 0; i < len; ++i) f[i] = g[i] = cpx(0, 0);
for(int i = 0; i < n; ++i) f[i] = cpx(a[i], 0);
for(int i = 0; i < m; ++i) g[i] = cpx(b[i], 0);
FFT(f, len, 1);
FFT(g, len, 1);
for(int i = 0; i < len; ++i) f[i] = f[i] * g[i];
FFT(f, len, -1);
for(int i = 0; i < len; ++i) c[i] = (int)(f[i].x+0.5);
}
int n, m, k;
int id[300], c[10][N], A[10][N];
int s[N], t[N];
int f[N], g[N];
signed main() {
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d%d%d", &n, &m, &k);
for(int i = 0;i <= 9;++i)
id[i] = i;
for(int i = 0;i < n;++i)
scanf("%1d",&s[i]);
for(int i = 0;i < m;++i)
scanf("%1d",&t[i]);
for(int i = 0; i < n; ++i) {
s[i] = id[(int)s[i]]; t[i] = id[(int)t[i]];
++c[(int)s[i]][Max(i-k, 0)];
--c[(int)s[i]][Min(i+k+1, n)];
}
for(int i = 1; i < n; ++i) for(int j = 0; j <= 9; ++j) c[j][i] += c[j][i-1];
for(int o = 0; o <= 9; ++o) {
for(int i = 0; i < n; ++i)
f[i] = c[o][i] ? 0 : 1;
for(int i = 0; i < m; ++i)
g[i] = t[i] == o ? 1 : 0;
std::reverse(g, g + m);
mul(f, g, A[o], n, m);
}
int ans = 0;
for(int i = m-1; i < n; ++i){
bool k = 1;
for(int j = 0;j <= 9;++j)
k = k & !A[j][i];
if(k)
++ans;
}
printf("%d\n", ans);
return 0;
}