AGC024 记录
发现AT还是永远的神,但是光写不记录的话总感觉效果不大,因此从AGC024开始估计后面每一场都会有记录虽然大概率还是咕,前面一些场的记录/题解有时间就补上
题面很好搜,所以就不放了。
A:可以发现是呈指数级增长的,暴力模拟。要不以后A、B就不放上来了
B:跳了,没啥意思,非常一眼的结论
C:树状数组题(实际上可以用桶做到O(n))
在每一个位置上开一个vector,表示这个位置需要变成的数有哪些。
假设已经知道了 \(x_{i+1}\) 需要变成的数是 \(y_1,y_2...,y_n\)
那么在 \(x_i\) 需要变成的数则是 \(y_1-1,y_2-1...y_n-1,a_i\) (排过序且去过重的)。
然后统计一下每个vector的size,答案就是 \(\sum^{n}_{i=1}siz_i\)。
同时根据上面的性质也可以发现 \(a_i-a_{i-1}\leq 1\),且 \(a_1=0\),否则无解。
不难发现上面的过程可以打tag+树状数组优化一下,比较简单就不详细解释了。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il int read(char *s){
int len=0;
register char ch=getchar();
while(ch==' '||ch=='\n') ch=getchar();
while(ch!=' '&&ch!='\n'&&ch!=EOF) s[++len]=ch,ch=getchar();
return len;
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=1e6+7;
ll n,x[MAXN],a[MAXN];
int t[MAXN];
#define lowbit(x) x&(-x)
void add(int k,int val){
if(!k) return;
for(;k<5e5;k+=lowbit(k)) t[k]+=val;
}
il int ask(int k){
int ans=0;
for(;k;k-=lowbit(k)) ans+=t[k];
return ans;
}
il int query(int l,int r){
if(l>r) return 0;
if(!r) return 0;
return ask(r)-ask(l-1);
}
int main(){
n=read();
a[0]=-1;
for(ri i=1;i<=n;++i){
a[i]=read();
if(a[i]-a[i-1]>1){
return !puts("-1");
}
}
if(a[1]!=0) return !puts("-1");
ll ans=0;
for(ri d=0,i=n;i;++d,--i){
if(!query(d+a[i],d+a[i]))
add(d+a[i],1);
ans+=query(d+1,5e5);
}
print(ans);
return 0;
}
D:挺不错一道题,如果想到了可以很轻松的写出来。
首先可以注意到,如果最后的树的直径为 \(L\),那么至少有 \(\lceil\frac{L}{2}\rceil\) 种本质不同的树,因为至少有 \(\lceil\frac{L}{2}\rceil\) 种树它们的最深深度不一样。
然后可以发现,这个下界非常好卡,当 \(n\) 是奇数的时候树的结构是关于一个一个顶点中心对称的,而当 \(n\) 是偶数的时候树的结构是关于一条边轴对称的。
因此可以枚举每一条边和每一个点,以它为根分别dfs一遍,每种情况的本质不同的树的数量就是整棵树的深度,叶子结点数则分别记录一下每一层儿子最多的点,然后从根往下递推。
总复杂度 \(O(n^2)\),不知道为啥只开了 \(n\leq 100\)
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il int read(char *s){
int len=0;
register char ch=getchar();
while(ch==' '||ch=='\n') ch=getchar();
while(ch!=' '&&ch!='\n'&&ch!=EOF) s[++len]=ch,ch=getchar();
return len;
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
#define pll pair<ll,ll>
#define fir first
#define sec second
ll mx;
pll ans=(pll){(ll)1e18,(ll)1e18};
const int MAXN=128;
int n;
struct edge
{
int u,v;
}e[MAXN];
vector<int> g[MAXN];
ll f[MAXN],pos;
void dfs(int u,int fa,int dep){
if(fa) f[dep]=max(f[dep],g[u].size()-1);
else f[dep]=max(f[dep],g[u].size());
mx=max(mx,dep);
for(ri i=0;i<g[u].size();++i){
int v=g[u][i];
if(v==fa) continue;
dfs(v,u,dep+1);
}
}
void solve_point(){
for(ri i=1;i<=n;++i){
memset(f,0,sizeof(f));
mx=0;
dfs(i,0,1);
ll c=1,tot=0;
for(ri i=1;i<mx;++i){
tot+=c;
c*=f[i];
}
pll now=(pll){mx,c};
if(now<ans){
ans=now;
pos=i;
}
}
}
void solve_edge(){
for(ri i=1;i<n;++i){
memset(f,0,sizeof(f));
mx=0;
int u=e[i].u,v=e[i].v;
dfs(u,v,1);
dfs(v,u,1);
ll c=2,tot=0;
for(ri i=1;i<mx;++i){
tot+=c;
c*=f[i];
}
pll now=(pll){mx,c};
if(now<ans){
ans=now;
pos=n+i;
}
}
}
int main(){
n=read();
for(ri i=1;i<n;++i){
int u=read(),v=read();
g[u].push_back(v),g[v].push_back(u);
e[i]=(edge){u,v};
}
solve_point();
solve_edge();
printf("%lld %lld\n",ans.fir,ans.sec);
// print(pos);
return 0;
}
E:高妙DP
首先第一个可以想到的,可以把整个操作看成每次往当前的序列中插入一个新的值,要满足每次插入之后字典序变大。
假设当前插入的数为 \(a_i\) ,有两种情况:
1.插入到末尾
这个一定合法。
2.插入到 \(a_j\) 前面
需要满足 \(j<i,a_j\leq a_i\)
但是当 \(a_j=a_i\) 的时候会算重,如何不算重先不考虑,后面有办法。
于是可以把每次操作看成一个二元组 \((x_i,y_i)\) ,表示它是第 \(x_i\) 次插入的,值为 \(y_i\)。
然后可以建一个模型,把 \(a_i\) 插入到 \(a_j\) 前面则由 \(j\) 向 \(i\) 连一条边,初始结点是 \(0\)。
不难发现可以构成一棵树,而且不考虑 \(a_i=a_j\) 的情况下每一棵树都能对应一种唯一方案。
实际上,当有一串连续的相同的值的时候,只要钦定下一次只能放在这个连续串的末尾,便能不算重了,在树上就是必须满足 \(x_i>x_j,y_i>y_j\) 才能连边。
那么可以做一个dp,\(f_{i,j}\) 表示 \(i\) 个点的子树中根结点的权值为 \(j\) 的方案数,最后的答案便是 \(f_{n+1,0}\)。
首先先预处理一下 \(i=1\) 的情况,\(f_{1,j}=1\),非常显然。
对于 \(f_{i,j},i>1\) 的情况,它必然存在一个儿子的 \(x\) 是 \(2\)。
去枚举以这个儿子为根的子树大小(如果枚举任意一颗子树而非根为 \(2\) 的子树是会算重的)\(l\),以及其权值 \(r\),那么它对 \(f_{i,j}\) 的贡献应该是:\(C^{i-2}_{l-1}\times f_{l,r}\)。
其中 \(C^{i-2}_{l-1}\) 是除去原树中 \(1,2\) 之后的 \(i-2\) 个值中选出 \(l-1\) 个来分配给这棵子树的方案数。
所以有:
\(
\begin{aligned}
f_{i,j}&=\sum^{i-1}_{l=1}\sum^{k}_{r=j+1}C^{i-2}_{l-1}\times f_{l,r}\\
&=\sum^{i-1}_{l=1}C^{i-2}_{l-1}\sum^{k}_{r=j+1}f_{l,r}
\end{aligned}
\)
不难发现后面的 \(\sum^{k}_{r=j+1}f_{l,r}\) 可以用后缀和优化。
时间复杂度 \(O(n^3)\)
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=305;
ll C[MAXN][MAXN],f[MAXN][MAXN],pre[MAXN][MAXN],n,mod,k;
void init(){
C[0][0]=1;
for(ri i=1;i<=300;++i){
C[i][0]=1;
for(ri j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
for(ri i=k;~i;--i) f[1][i]=1,pre[1][i]=(pre[1][i+1]+f[1][i])%mod;
for(ri i=2;i<=n+1;++i){
for(ri j=0;j<=k;++j){
for(ri l=1;l<i;++l){
(f[i][j]+=f[i-l][j]*C[i-2][l-1]%mod*pre[l][j+1])%=mod;
}
pre[i][j]=f[i][j];
}
for(ri j=k;~j;--j) (pre[i][j]+=pre[i][j+1])%=mod;
}
}
int main(){
n=read(),k=read(),mod=read();
init();
print(f[n+1][0]);
return 0;
}
F:真·子序列自动机
NT了,死活调不出来DP,代码咕了
时隔四个月,又回来补题了
首先有个非常容易想到的思路:对于每一个串,都跑一下它的所有子序列,让它所有的子序列出现次数+1。
设 \(f(A,B)\) 表示已经匹配了字符串 \(A\) 的部分,还剩下字符串 \(B\) 的部分没有匹配。
转移非常简单:\(
\left \{
\begin{aligned}
&f(A+'1',C)+=f(A,B)\\
&f(A+'0',D)+=f(A,B)\\
&f(A,\not O)+=f(A,B)\\
\end{aligned}
\right.
\)
其中 \(C\) 是字符串 \(A\) 去掉最左边到第一个 $ '1' $ 的位置。
举个例子:
\(A=0001011101\),
那么 \(C=011101\)。
\(D\) 则同理去掉 最左边到第一个 $ '0' $。
对于每一个字符串,每一个自动机上的边形成的图都是一张 \(DAG\) ,建出来跑一边拓扑就可以了。
总状态数 \(O(n2^n)\) ,转移是 \(O(1)\) 的,因此总复杂度是 \(O(n2^n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
int f[21][1<<21],g[1<<21][2],lg[1<<21],n,k,ans;
int main(){
// freopen("rand.in","r",stdin);
// freopen("1.out","w",stdout);
n=read(),k=read();
for(ri i=0;i<=n;++i)
for(ri j=0;j<1<<i;++j)
scanf("%1d",&f[i][1<<i^j]);
lg[0]=-1;
for(ri i=1;i<(2<<n);++i) lg[i]=lg[i>>1]+1;
for(ri i=2;i<(2<<n);++i){
if(i>>(lg[i]-1)&1){
g[i][1]=i^1<<lg[i];
g[i][0]=g[g[i][1]][0];
}
else{
g[i][0]=i^3<<lg[i]-1;
g[i][1]=g[g[i][0]][1];
}
}
for(ri i=n;i;--i){
for(ri j=1<<i;j< 2<<n ;++j){
if(!f[i][j]) continue;
int x=j>>i,y=(j&((1<<i)-1))^(1<<i);
if(g[y][0]) f[ lg[g[y][0]] ][(x<<1^1) << lg[g[y][0]] ^ g[y][0]]+=f[i][j];
if(g[y][1]) f[ lg[g[y][1]] ][(x<<1) << lg[g[y][1]] ^ g[y][1]]+=f[i][j];
f[0][x]+=f[i][j];
}
}
ans=1;
for(ri i=0;i<(2<<n);++i)
if(f[0][i]>=k&&lg[i]>lg[ans]) ans=i;
for(ri i=lg[ans]-1;~i;--i)
printf("%d",ans>>i&1);
return 0;
}

浙公网安备 33010602011771号