Lg7 月赛(构造,树形 dp)
咕了一万年的题解报告了已经。
T1
傻逼爆搜过了
T2
草这贪心连我这只菜狗都会
T3
排行榜里人均切掉这个构造
首先通过统计一个数在每层是否出现可以得出这个数的支配区间。再考虑处理出这个数在这个区间里的相对位置,这个可以通过枚举一个区间,判断这个点出现次数是否等于支配区间减去枚举区间。
然后我们现在已经知道了支配区间和相对位置就可以向这个区间插入进去,然后这么做就能还原序列。
至于为什么这么做是对的,首先对于后面的插区间操作,我们从小到大插,那么剩余区间中必然有一个是留给下一个数的,所以正确性得以保证。
插区间还用了珂朵莉……
#include <bits/stdc++.h>
using namespace std;
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9'){f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 1005;
int ci[np][np],k[np];
int len[np],pos[np];
int a[np];
struct Node{
int l,r;
int sum;
friend bool operator<(Node a,Node b)
{
return a.l < b.l;
}
};
set<Node> s;
inline bool cmp(int a,int b)
{
return len[a] > len[b];
}
// int Flag = 0;
inline void split(int len_,int pos_,int x)
{
// if(len_ == 2 && pos_ == 1 && x == 2) Flag = 1;
for(set<Node>::iterator it = s.begin();it!=s.end();it++)
{
if((*it).sum) continue;
int l = (*it).l;
int r = (*it).r;
if(len_ == r - l + 1)
{
// printf("")
s.erase(it);
// if(Flag)printf("%d-%d",l,r);//printf("%d ",l + pos_ - 1);
// if(Flag) printf("%d ",l + pos_ + 1);
if(l <= l + pos_-2)
{
// if(Flag) printf("*");
s.insert((Node){l,l + pos_-2,0});
}
if(l + pos_ <= r)
{
// if(Flag) printf("*");
s.insert((Node){l + pos_,r,0});
}
s.insert((Node){l + pos_ - 1,l + pos_ -1,x});
// return ;
return;
}
}
// for(set<Node>::iterator it = s.begin();it!=s.end();it++)
// {
// printf("%d %d %d\n",(*it).l,(*it).r,(*it).sum);
// }
// printf("------------------\n");
// if(Flag) Flag = 0;
}
signed main()
{
int n;
read(n);
s.insert((Node){1,n,0});
for(int i=1;i<=n;i++)
{
for(int j=1,x;j<=n-i + 1;j++)
{
read(x);
ci[i][x]++;
}
}
for(int i=1;i<=n;i++)
{
// int la = 0;
for(int q = n;q >= 1;q--)
{
if(ci[q][i])
{
len[i] = q;
break;
}
}
// len[i] = la;
}
// for(int i=1;i<=n;i++)
for(int i=1;i<=n;i++)
{
for(int q = 1;q <= len[i];q ++ )
{
if(ci[q][i] == len[i] - q + 1)
{
k[i] = q;
break;
}
}
}
// for(int i=1;i<=n;i++)
// printf("%d ",len[i]);
// printf("\n");
// for(int i=1;i<=n;i++)
// printf("%d ",k[i]);
// printf("\n");
for(int i=1;i<=n;i++) a[i] = i;
sort(a + 1,a + 1 + n);
for(int q=1;q<=n;q++)
{
int i = a[q];
// printf("%d ",i);
split(len[i],k[i],i);
}
for(set<Node>::iterator it = s.begin();it!=s.end();it++)
printf("%d ",(*it).sum);
}
T4
草我只觉得这个题神仙,真是不管什么玩意都能 dp 啊。
首先你得分析出一些性质
01 分界线处类型不一样
对于 \(or-0,and-1\) 若要维持原状态必须保证周围和自己一直处于原状态
一个连通块形成最终状态只有两种过程,一是内部存在一个最终状态,或者是相邻结点在初始状态的时候做了一波极限一换一
有了这些良好的性质就可以直接进行 dp 了(草这性质良好???这东西能 dp???
直接对原值进行 dp
设 \(dp[i][j][k][l]\) 表示当前在 i 点,j 表示初值和终值是否相等,k 表示是否强制周围节点一直保持同样状态,l 表示连通块限制是否满足
这个 dp 是 \(O(n)\) 的,但看着这个恶心的想吐的状态就知道不好转移。
但是这 8 种状态中只有 4 种有用
相等,强制,满足
相等,不强制,满足
不相等,不强制,满足
不相等,不强制,不满足
然后慢慢分类讨论转移就是了(我也不知道这阴间做法谁提出来的,总之就是非常牛逼
(其实更阳间一点的状态就是设 \(dp[x][点的类型][初始值][是否满足限制]\) 这个就阳间很多,就是分类讨论挺麻烦的,但比赛的时候还是后者更靠谱一点
#include<bits/stdc++.h>
using namespace std;
#define int long long
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
const int np = 2e5 + 5;
const int mod = 998244353;
int dp[np][10];
int g[np],tit;
//
int a[np],head[np],ver[np * 2],nxt[np * 2];
int n;
inline void add(int x,int y)
{
ver[++tit] = y;
nxt[tit] = head[x];
head[x] = tit;
}
// dp[x][0] 表示 x 的子树初始权值相等,强制型结点,满足限制
// dp[x][1] 初始权值相等,非强制,满足限制
// dp[x][2] 初始权值不等,非强制,满足限制
// dp[x][3] 初始权值不等,非强制,不满足限制
inline void dfs(int x,int ff)
{
dp[x][0] = 1,dp[x][3] = 1,dp[x][1] = 1;
for(int i=head[x],v;i;i=nxt[i])
{
v = ver[i];
if(v == ff) continue;
dfs(v,x);
for(int i=0;i <= 4;i ++) g[i] = 0;
if(a[x] == a[v])
{
g[0] = dp[x][0] * (dp[v][0] + dp[v][1]);//初始权值相等,结点为强制型
g[1] = dp[x][1] * (dp[v][0] + dp[v][1] + dp[v][2] + dp[v][3]);
g[2] = dp[x][2] * (dp[v][1] + dp[v][2] + dp[v][3]) + dp[x][3] * (dp[v][1] + dp[v][2]);
g[3] = dp[x][3] * dp[v][3];
}
else
{
g[1] = dp[x][1] * (dp[v][1] + dp[v][2]);
g[2] = dp[x][2] * (dp[v][3] + dp[v][1] + dp[v][2]) + dp[x][3] * (dp[v][2] + dp[v][3]);
g[3] = dp[x][3] * dp[v][1];
}
for(int q= 0 ;q <= 3;q ++) dp[x][q] = g[q],dp[x][q] %= mod;
}
}
signed main()
{
read(n);
for(int i=1 ;i <= n;i ++) read(a[i]);
for(int i=1,a,b;i <= n - 1;i ++)
{
read(a),read(b);
add(a,b),add(b,a);
}
dfs(1,0);
printf("%lld",(dp[1][0] + dp[1][1] + dp[1][2])%mod);
}
你说比赛越打越菜是怎么回事?