星期一:
睿抗初赛,遇到了个不会的判章鱼环,最后40min一直憋着de,最后差8分的段错误,感觉差点
星期二:
贴 ABC 160 E atc传送门
题意:吃a个红苹果和b个绿苹果,还有c个无色苹果可以平替任意颜色苹果,问最大值
思路:开局没想到优先队列,被折磨了一个多小时。。
补 ABC 172 D atc传送门
思路:质因数分解可以冲过去(<=2000ms,正解需要考虑到等差数列
考虑每个因子对答案的贡献,如 n==7,2是2,4,6的因子,对答案的贡献分别是2,4,6,可以发现这是一个等差数列,用等差数列公式计算即可
代码(分解质因数 如下:
const int N=1e7+10,M=210;
ll n;
int p[N],idx;
bool vi[N];
void getp(int x){
for(int i=2;i<=x;i++){
if(!vi[i]) p[++idx]=i;
for(int j=1;1ll*i*p[j]<=x;j++){
vi[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
}
void solve(){
cin >> n;
getp(n);
ll ans=0;
for(int i=1;i<=n;i++){
ll f=1;int t=i;
for(int j=1;j<=idx && p[j]<=t/p[j];j++){
int tt=1;
while(t%p[j]==0){
t/=p[j];
tt++;
}
f*=tt;
}
if(t>1) f*=2;
ans+=f*i;
}
cout << ans;
}
代码(等差数列 如下:
ll n;
void solve(){
cin >> n;
ll ans=0;
for(int i=1;i<=n;i++) ans+=(i+n/i*i)*(n/i)/2;
cout << ans;
}
补 ABC 175 D atc传送门
思路:整个排列一定是由若干环组成的,目的是找一个起点在环里走【1,k】的步数获得最大分数
根据得分规则处理出 sc【i】【j】表示从 i点起走 j步获得的分数,再处理出单调不减性可省去后面的枚举操作。dfs找环并处理出每个点所在环的长度,最后即可枚举起点计算答案
计算答案时存在多种情况,总结下就是不一定走满 k步,我漏掉的情况是可走多圈时,最后一圈可不走完而取最优值
代码如下:
ll n;
ll c[5050],p[5050],len[5050];
ll sc[5050][5050];
bool vi[5050];
ll cnt;
inline void dfs(int x){
if(vi[x]) return ;
vi[x]=1;
cnt++;
dfs(p[x]);
len[x]=cnt;
}
void solve(){
ll k; cin >> n >> k;
for(int i=1;i<=n;i++) cin >> p[i];
ll mi=LLONG_MIN;
for(int i=1;i<=n;i++) cin >> c[i],mi=max(c[i],mi);
if(mi<0){cout << mi; return ;}
for(int i=1;i<=n;i++){
if(vi[i]) continue;
cnt=0;
dfs(i);
}
ll ma=LLONG_MIN;
for(int j=1;j<=n;j++){
for(int i=1;i<=n;i++){
sc[i][j]=sc[p[i]][j-1]+c[p[i]];
if(j<=k) ma=max(sc[i][j],ma);
}
}
for(int i=1;i<=n;i++){
for(int j=2;j<len[i];j++) sc[i][j]=max(sc[i][j-1],sc[i][j]);
} //单调不减,若能走j步那么也能选择走j-1步
for(int i=1;i<=n;i++){
if(k<len[i]) ma=max(sc[i][k],ma);
else if(k>=len[i]){
ll cp=max(0ll,k/len[i]*sc[i][len[i]]);
cp+=max(0ll,sc[i][k%len[i]]);
ma=max(cp,ma); //走满
cp=max(0ll,(k/len[i]-1)*sc[i][len[i]]);
cp+=sc[i][len[i]-1];
ma=max(cp,ma); //少走满一圈,最后一圈取最大
}
}
cout << ma;
}
补 ABC 146 E atc传送门
思路:i到 j+1区间为合法序列的条件是 ( a【i】- a【j】)%k== i - j,不妨将右边也模上 k
移项后变为 (a【i】-i)%k==(a【j】- j)%k,用mp存下之前的(a【i】-i)%k的值即可
注意初始时mp【0】=1,且窗口有效长度<=k
代码如下:
const int N=2e5+10,M=210;
ll n;
ll a[N];
void solve(){
ll k; cin >> n >> k;
unordered_map<int,int>mp;
ll ans=0;
mp[0]++;
for(int i=1;i<=n;i++){
if(i>=k) mp[(a[i-k]-i+k)%k]--;
cin >> a[i];
a[i]+=a[i-1];
ans+=mp[(a[i]-i)%k]++;
}
cout << ans;
}
星期三:
补 ABC 248 F atc传送门
思路:dp【i】【j】【0/1】表示考虑到第 i列删去了 j条边图是否联通的方案数
详细思路见:abc 248 F 题解 - 洛谷专栏 (luogu.com.cn)
代码如下:
ll n;
int P;
ll dp[3030][3030][2];
void solve(){
cin >> n >> P;
dp[1][0][1]=1,dp[1][1][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<n;j++){
(dp[i+1][j+2][0]+=dp[i][j][1]*2)%=P;
(dp[i+1][j+1][1]+=dp[i][j][1]*3)%=P;
(dp[i+1][j][1]+=dp[i][j][1])%=P;
(dp[i+1][j+1][0]+=dp[i][j][0])%=P;
(dp[i+1][j][1]+=dp[i][j][0])%=P;
}
}
for(int i=1;i<n;i++) cout << dp[n][i][1] << " ";
}
河南萌新联赛2024 一 D 牛客传送门
思路:按位计算贡献,找出规律,每两个数中有一个1,每四个数中有两个2,每八个数有四个4
代码如下:
const int mod=998244353;
ll n;
inline ll cal(ll x,int i){
ll res=x/(1ll<<i+1)*(1ll<<i)%mod;
x%=(1ll<<i+1);
x-=(1ll<<i)-1;
if(x>0){
res+=x;
if(x==(1ll<<i+1)) res--;
}
return res;
}
void solve(){
int t; cin >> t;
while(t--){
ll l,r; cin >> l >> r;
ll ans=0;
for(int i=0;i<=60;i++) (ans+=cal(r,i)-cal(l-1,i))%=mod;
cout << ans << "\n";
}
}
B 朵拉爱探险 牛客传送门
思路:和昨天的题有点相似,区别是此题没有排列的约束,所以并不全是闭环
路径有两种可能,一种是树形连通块,找出最长的一条路径即可,第二种为环形,但并不只是环,可能还有链依附在上面,需要求出环的长度及其最长的一条链
先拓扑排序判环,链状图的最长路径用dfs求即可,环状图可存入map里,对链上点dfs求最长链并统计环上点个数,即环的长度
代码如下:
const int N=2e6+10,M=210;
ll n;
int a[N],in[N],fa[N];
int tin[N];
vector<int>fr[N];
bool tr[N],d[N];
inline int fnd(int x){
return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
void tp(){
queue<int>qu;
for(int i=1;i<=n;i++)
if(!tin[i]) qu.push(i),tr[i]=1;
while(!qu.empty()){
int t=qu.front(); qu.pop();
int v=a[t];
tin[v]--;
if(!tin[v]) qu.push(v),tr[v]=1; //tr[i]代表i点是否在链上
}
}
int len[N];
inline int dfs(int x){
if(!in[x]) return 1;
len[x]=1;
int ma=0;
for(int f:fr[x]){
if(len[f]) ma=max(len[f],ma);
else ma=max(dfs(f),ma);
}
len[x]+=ma;
return len[x];
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++){
cin >> a[i];
in[a[i]]++,tin[a[i]]++;
fr[a[i]].push_back(i);
int u=fnd(i),v=fnd(a[i]);
if(u!=v) fa[u]=v;
}
tp();
for(int i=1;i<=n;i++) if(!tr[i]) d[fnd(i)]=1; //此连通块有环
int ans=0;
map<int,vector<int>>mp;
for(int i=1;i<=n;i++){
if(!d[fnd(i)]) ans=max(dfs(i),ans); //无环,选择链上最长路径
else mp[fnd(i)].push_back(i); //有环,先把连通块存入map里
}
for(auto [f,ve]:mp){
int huan=0,lian=0;
for(int j:ve){
if(!tr[j]) huan++; //在环上,环长度++
else lian=max(dfs(j),lian); //在链上,选取最长链
}
ans=max(huan+lian,ans);
}
cout << ans;
}
补河萌 2024 一 J 牛客传送门
思路:这个箭头方向不好操作,将矩阵上下翻转,就变成了找指左上方的箭头,可以动态维护
处理出二维前缀和,如果dp【i-1】【j-1】是一个箭头尾,那么i,j接在后面的条件是三个点为1,其余点为0,满足则dp【i】【j】= dp【i-1】【j-1】+1
还有种是dp【i】【j】= dp【i-1】【j-1】,但目前没想到这种转移的情况长什么样
代码如下:
ll n;
char c[5050][5050];
int sum[5050][5050],dp[5050][5050];
int get(int x1, int y1, int x2, int y2)
{
return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
void solve(){
int m; cin >> n >> m;
for(int i=n;i;i--)
for(int j=1;j<=m;j++)
cin >> c[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+(c[i][j]=='1');
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(c[i][j]=='1') dp[i][j]=1,ans=max(dp[i][j],ans);
if(!dp[i-1][j-1] || c[i][j]!='1') continue;
int k=dp[i-1][j-1];
bool if1=1,if0=1;
if(c[i][j-k]!='1' || c[i-k][j]!='1') if1=0;
if(sum[i][j]-sum[i-k][j]-sum[i][j-1]+sum[i-k][j-1]-1) if1=0;
if(sum[i][j]-sum[i][j-k]-sum[i-1][j]+sum[i-1][j-k]-1) if1=0;
if(if1) dp[i][j]=dp[i-1][j-1]+1;
else{
if(c[i-k+1][j]!='1' || c[i][j-k+1]!='1') if0=0;
if(get(i-k+1,j-k+1,i,j)!=3*k-2) if0=0;
if(if0) dp[i][j]=dp[i-1][j-1];
}
ans=max(dp[i][j],ans);
}
}
cout << ans;
}
星期四:
洛谷 tarjan板子题 洛谷传送门
思路:tarjan求强连通分量就完事了
D14 强连通分量 Tarjan 算法_哔哩哔哩_bilibili
代码如下:
const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk;
bool ins[N];
int scc[N],sz[N],cnt;
vector<int>ve[N];
void tarjan(int x){
dfn[x]=low[x]=++tot;
sk.push(x); ins[x]=1;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[v],low[x]);
}else if(ins[v]) low[x]=min(dfn[v],low[x]);
}
if(dfn[x]==low[x]){
int y; cnt++;
do{
y=sk.top();
ins[sk.top()]=0; sk.pop();
scc[y]=cnt;
sz[cnt]++;
}while(y!=x);
}
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
ll ans=0;
for(int i=1;i<=cnt;i++) ans+=(sz[i]>1);
cout << ans;
}
牛客暑假多校2024 二 E 牛客传送门
题意:选一个严格小于 x的 y使 gcd (x,y) == x^y
思路:开始想到若 x为奇,y可为x-1,然后忘记了y需严格小于x,交了个若x为偶则y为x+1
想到若 x为偶且第二位为1,可构造 y为 x-2,到这可猜出结论 y为 x减去二进制最低位1
代码如下:
void solve(){
ll x; cin >> x;
ll y=0;
for(int i=0;i<=60;i++)
if(x&(1ll<<i)){y=x-(1ll<<i); break;} //也可写为y=x-(x&-x)
if(!y) cout << "-1\n";
else cout << y << "\n";
}
H 牛客传送门
题意:给一操作字符串,问其子串数,子串满足执行路径经过 (x,y)点
思路:处理出前缀和是第一步。开始想过线段树二分,但无法处理tag及合并操作,故作罢。
从 i点开始走,找到第一个到达目标点的 j,可以得知对于纵向和横向步数的要求,加上 i-1前缀和造成的偏移,得到目标坐标 tx和 ty,可二分搜索。把所有到达的坐标存入multiset即可实现
代码如下:
const int N=2e6+10,M=210;
ll n;
int su[N],sd[N],sl[N],sr[N];
struct nod{
int ud,rl; //上下和右左的差值
int id;
bool operator <(const nod &b)const{ //重写比较,满足二分要求
if(ud!=b.ud) return ud<b.ud;
if(rl!=b.rl) return rl<b.rl;
return id<b.id;
}
};
void solve(){
int x,y; cin >> n >> y >> x; //后面的代码里把x处理为行,y处理为列,所以输入反过来
string s; cin >> s; s=" "+s;
if(!x && !y){cout << (n+1)*n/2; return ;} //特判原点
multiset<nod>mt;
for(int i=1;i<=n;i++){
su[i]=su[i-1]+(s[i]=='W');
sd[i]=sd[i-1]+(s[i]=='S');
sl[i]=sl[i-1]+(s[i]=='A');
sr[i]=sr[i-1]+(s[i]=='D');
mt.insert({su[i]-sd[i],sr[i]-sl[i],i});
}
ll ans=0;
for(int i=1;i<=n;i++){
int tx=x+su[i-1]-sd[i-1];
int ty=y+sr[i-1]-sl[i-1];
if(i>1) mt.erase(mt.find({su[i-1]-sd[i-1],sr[i-1]-sl[i-1],i-1}));
auto it=mt.lower_bound({tx,ty,0});
if(it==mt.end()) continue;
nod j=*it;
if(j.ud!=tx || j.rl!=ty) continue;
ans+=n-j.id+1; //起点为i,终点j-n的路径都合法
}
cout << ans;
}
洛谷 P2812 tarjan缩点 洛谷传送门
思路:tarjan先处理出强联通分量,把一个分量视作一个点,计算每个点的出度入度,入度为0的点即为母机。 第二问为添加最少边使其成环,答案为入度为0和出度为0的点的数量的 max,可推导也可画图验证
若强连通量只有一个,则无需添边而不是1,记得特判!!(wa了一发
代码如下:
const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
void tarjan(int x){
dfn[x]=low[x]=++tot;
sk.push(x); ins[x]=1;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[v],low[x]);
}else if(ins[v]) low[x]=min(dfn[v],low[x]);
}
if(dfn[x]==low[x]){
int y; ++cnt;
do{
y=sk.top();
ins[sk.top()]=0,sk.pop();
ssc[y]=cnt;
sz[cnt]++;
}while(y!=x);
}
}
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
int x;
while(cin >> x && x) ve[i].push_back(x);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int x=1;x<=n;x++){
for(int v:ve[x]){
if(ssc[x]!=ssc[v]){
in[ssc[v]]++;
out[ssc[x]]++; //处理缩点后的入度和出度
}
}
}
int a=0,b=0;
for(int i=1;i<=cnt;i++)
a+=!in[i],b+=!out[i];
cout << a << "\n";
if(cnt==1) cout << 0; //关键的特判
else cout << max(a,b);
}
洛谷 P2341 洛谷传送门
思路:和上题大体很像,求明星牛所在分量时需注意,条件并不是入度为cnt-1,而是出度为0, 出度为0这条件乍一看有点奇怪,实际上是对的,而入度为cnt-1因为爱慕可传递,所以是错的
还需注意出度为0的分量只能有一个,大于一个则无明星牛
代码如下:
const int N=2e6+10,M=210;
ll n;
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
void tarjan(int x){
dfn[x]=low[x]=++tot;
sk.push(x); ins[x]=1;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[v],low[x]);
}else if(ins[v]) low[x]=min(dfn[v],low[x]);
}
if(dfn[x]==low[x]){
int y; ++cnt;
do{
y=sk.top();
ins[sk.top()]=0,sk.pop();
ssc[y]=cnt;
sz[cnt]++;
}while(y!=x);
}
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=m;i++){
int a,b; cin >> a >> b;
ve[a].push_back(b);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
if(cnt==1){cout << n; return ;}
for(int x=1;x<=n;x++){
for(int v:ve[x])
if(ssc[x]!=ssc[v]){
in[ssc[v]]++;
out[ssc[x]]++;
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
if(!out[i]){
if(!ans) ans=sz[i];
else{ans=0; break;}
}
}
cout << ans;
}
洛谷 P3387 洛谷传送门
思路:缩点后在DAG上跑dfs秒了,dfs参照朵拉求链长
代码如下:
const int N=1e4+10,M=210;
ll n;
int a[N],sum[N];
int dfn[N],low[N],tot;
stack<int>sk; bool ins[N];
int ssc[N],sz[N],cnt;
vector<int>ve[N];
int in[N],out[N];
set<int>fr[N];
void tarjan(int x){
dfn[x]=low[x]=++tot;
sk.push(x); ins[x]=1;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[v],low[x]);
}else if(ins[v]) low[x]=min(dfn[v],low[x]);
}
if(dfn[x]==low[x]){
int y; ++cnt;
do{
y=sk.top();
ins[sk.top()]=0,sk.pop();
ssc[y]=cnt;
sz[cnt]+=a[y];
}while(y!=x);
}
}
int dfs(int x){
if(!in[x]) return sz[x];
int res=sz[x],ma=0;
for(int f:fr[x]){
if(sum[f]) ma=max(sum[f],ma);
else ma=max(dfs(f),ma);
}
res+=ma;
return sum[x]=res;
}
void solve(){
int m; cin >> n >> m;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=m;i++){
int a,b; cin >> a >> b;
ve[a].push_back(b);
}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int x=1;x<=n;x++){
for(int v:ve[x])
if(ssc[x]!=ssc[v]){
in[ssc[v]]++;
out[ssc[x]]++;
fr[ssc[v]].insert(ssc[x]);
}
}
int ans=0;
for(int i=1;i<=cnt;i++) if(!sum[i]) ans=max(dfs(i),ans);
cout << ans;
}
星期五:
上午个人赛 签了个到
补 ABC 211 E atc传送门
思路:状压解决不了此题,需暴搜,数据范围小且是连通块,时间可接受
使用vector<string>和set容器来判重
代码如下:
ll n;
vector<string>s;
set<vector<string>>st;
ll ans;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
void dfs(int lef){
if(st.find(s)!=st.end()) return ; //搜过此图
st.insert(s);
if(!lef){ans++; return ;}
vector<PII>ve; //接下来可以涂红的点
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
if(s[i][j]=='.'){
for(int k=0;k<4;k++){
int xx=i+dx[k],yy=j+dy[k];
if(xx<0 || xx>=n || yy<0 || yy>=n) continue;
if(s[xx][yy]=='r'){
ve.push_back({i,j});
break;
}
}
}
}
for(auto [x,y]:ve){
s[x][y]='r';
dfs(lef-1);
s[x][y]='.'; //回溯
}
}
void solve(){
int k; cin >> n >> k;
s.resize(n);
for(int i=0;i<n;i++) cin >> s[i];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(s[i][j]=='.'){
s[i][j]='r';
dfs(k-1);
s[i][j]='.'; //回溯
}
}
}
cout << ans;
}
补 ABC 230 F atc传送门
思路:乍一看题意很刁,仔细分析下合并操作,合并并不会影响总和即sum【n】,但如果合并第 i和 i+1的元素,会使得sum【i】消失,而已知前缀和数组,可得到原数组,于是题意可转化为在前缀和数组中有多少个不同的子序列,sum【n】必选
dp【i】表示 1-i的元素中有多少不同子序列,mp【i】表示 i值上次出现的位置
若 !mp【sum【i】】,dp【i】= dp【i-1】* 2 + 1 表示前 i-1的子序列数以及末尾加上sum【i】若 mp【sum【i】】= lst,dp【i】= dp【i-1】* 2 - dp【lst-1】,因为在 dp【lst-1】的序列末尾加上 sum【i】得到的序列数已经被计算过了
代码如下:
const int N=2e6+10,M=210;
const int mod=998244353;
ll n;
ll a[N];
ll dp[N];
void solve(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
a[i]+=a[i-1];
}
map<ll,int>mp;
for(int i=1;i<n;i++){
if(!mp[a[i]]) dp[i]=(dp[i-1]*2+1)%mod;
else dp[i]=(dp[i-1]*2-dp[mp[a[i]]-1]+mod)%mod; //减法记得+mod后再模
mp[a[i]]=i;
}
cout << (dp[n-1]+1)%mod; //算上空序列
}
星期六:
上午友谊赛2 退步一名
补 cf round629 div3 E cf传送门
思路:因为建树没建双向边被拿下了
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!警钟长鸣啊!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
代码如下:
const int N=2e6+10,M=210;
ll n;
vector<int>ve[N];
int dep[N],fa[N][20]; //1<<20 近似 1e6
void dfs(int u,int f){ //O(nlogn)
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=19;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:ve[u])
if(v!=f) dfs(v,u);
}
int lca(int u,int v){ //O(logn)
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
for(int i=19;i>=0;i--)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
void solve(){
int q; cin >> n >> q;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u); //建双向边建双向边建双向边建双向边建双向边建双向边
}
dfs(1,0);
while(q--){
int k; cin >> k;
int to=1;
bool if1=1;
while(k--){
int p; cin >> p;
if(dep[p]<=1) continue;
int an=lca(to,p);
if(dep[p]<=dep[to]){
if(p!=an && fa[p][0]!=an) if1=0;
}else{
if(an!=to) if1=0;
else to=fa[p][0];
}
}
if(if1) cout << "YES\n";
else cout << "NO\n";
}
}
牛客树形dp题单 D 牛客传送门
题意:丧尸舞会升级版
思路:dp【x】【0/1/2】表示 x点被 自己/儿子/父亲 覆盖所需代价
若被儿子覆盖,儿子中至少有一个 0就行,转移时记录下儿子 0和1的差值,取最小的补上就行
代码如下:
const int N=2e5+10,M=210;
ll n;
vector<int>ve[N];
int dp[N][3];
void dfs(int x,int f){
dp[x][0]=1;
dp[x][1]=dp[x][2]=0;
int mi=1e9;
for(int v:ve[x]){
if(v==f) continue;
dfs(v,x);
dp[x][0]+=min({dp[v][0],dp[v][1],dp[v][2]});
dp[x][2]+=min(dp[v][0],dp[v][1]);
dp[x][1]+=min(dp[v][0],dp[v][1]);
mi=min(dp[v][0]-dp[v][1],mi);
}
if(mi>0) dp[x][1]+=mi;
dp[x][1]=max(1,dp[x][1]); //如果没儿子,还是得靠自己
}
void solve(){
cin >> n;
for(int i=1;i<n;i++){
int u,v; cin >> u >> v;
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs(1,0);
cout << min(dp[1][0],dp[1][1]);
}
周日:
补 ABC 216 F atc传送门
思路:dp【i】【j】表示考虑到 i时,选取 b和为 j的方案数
先将 a和b按a从小到大排序,这样遍历时 a【i】就是 a中最大值,b【i】必选,那么需要知道在前 i-1个 b中选取使得和 <= a【i】- b【i】的方案数,其实就是背包dp的写法
代码如下:
const int mod=998244353;
ll n;
PII a[5050];
ll dp[5050][5050],sum[5050];
void solve(){
cin >> n;
int ma=0;
for(int i=1;i<=n;i++){
cin >> a[i].first;
ma=max(a[i].first,1ll*ma);
}
for(int i=1;i<=n;i++) cin >> a[i].second;
sort(a+1,a+n+1);
sum[0]=1;
for(int i=1;i<=n;i++){
// for(int j=1;j<=ma;j++){
// dp[i][j]=dp[i-1][j];
// if(a[i].second<=j) (dp[i][j]+=dp[i-1][j-a[i].second])%=mod;
// if(j<=a[i].first) (ans+=dp[i][j])%=mod;
// }
for(int j=a[i].second;j<=ma;j++) dp[i][j]=sum[j-a[i].second];
for(int j=a[i].second;j<=ma;j++) (sum[j]+=dp[i][j])%=mod;
}
ll ans=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=a[i].first;j++) (ans+=dp[i][j])%=mod;
cout << ans;
}
一维优化(dp【j】表示选取 b组成 j的方案数 :
const int mod=998244353;
ll n;
PII a[5050];
ll dp[5050];
void solve(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i].first;
for(int i=1;i<=n;i++) cin >> a[i].second;
sort(a+1,a+n+1);
ll ans=0;
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=a[i].first-a[i].second;j++) (ans+=dp[j])%=mod;
for(int j=5000;j>=a[i].second;j--) (dp[j]+=dp[j-a[i].second])%=mod;
}
cout << ans;
}