B-Cake 2_2024牛客暑期多校训练营6 (nowcoder.com)
欧拉定理 : V − E + F = 2 V - E + F = 2 V−E+F=2
v: 顶点,e:边数,f:面数
扩展若为 k 个联通分支 V − E + F = k + 1 V-E + F = k+1 V−E+F=k+1
线段相交的数量可以由小的那一侧顶点数推出。就可以画个图,发现里面的形状其实和外面一样,即里面有 n n n 个点,然后我们可以去看一条线被切割了 m i n ( n , n − k ) min(n,n-k) min(n,n−k) 份,然后 × n \times n ×n 就好。
最后推出答案为 2 ∗ k = n 2*k = n 2∗k=n 时候答案为 n n n 因为相交于重心,其余情况就是 n × m i n ( k , n − k ) + 1 n\times min(k,n-k) +1 n×min(k,n−k)+1 。
F-Challenge NPC 2_2024牛客暑期多校训练营6 (nowcoder.com)
由于是森林,即无环,然后很显然,当是一个菊花图,即 n − 1 n-1 n−1 个点与 另一个点相连的时候,是无法使得他的补图是连通的。因为补图中没有可以与 那个中心点相连的边。
然后我们考虑如果存在我们所要的连通的,如何去构造?
我们会发现其实同一层之间是可以在序列里面相邻,就可以直接按照顺序把深度丢入。而且在上面的约束下,仅有一棵树的时候必然链的长度最小为 4 4 4 ,我们可以按照深度 2 , 4...1 , 3... n 2,4 ...1,3...n 2,4...1,3...n 这样去构造,最后就可以得到答案。但是我们会发现,这其实是个森林,每个树都有链,而且可能 某个数的链长 < 4 <4 <4 是个菊花图,那此时其实我们可以直接连接直径,这样连接到最后的直径肯定是 ≥ 4 \ge 4 ≥4 。
但是大家会发现还会 w a wa wa 因为 n = 3 , m = 1 n=3,m=1 n=3,m=1 的时候我们没有考虑
例如有样例:
3 1
1 2
如果此时连边,会使得 2 与 3 相连,最后我们输出的时候可能是 2 1 3 ,显然是错误解,所以我们特判一下就好了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+10;
vector<int> e[N];
vector<int> dep[N];
int vis[N];
int mx ;
int root;
int deps[N];
int ls ;
void dfs(int u,int fa)
{ deps[u] = deps[fa] +1;
vis[u] = true;
for(auto v:e[u])
{ if(v==fa)
continue ;
dfs(v,u);
}
if(deps[u] > mx)
{
mx = deps[u];
root = u;
}
deps[u] = 0;
}
void dfs2(int u,int fa,int now)
{ dep[now].push_back(u);
for(auto v:e[u])
{
if(v==fa)
continue ;
dfs2(v,u,now+1);
}
}
void cal(int i)
{
mx = 0;
root = 0;
dfs(i,i);
int now = root;
mx = 0;
dfs(root,root);
if(ls == -1)
ls = root;
else
{
e[ls].push_back(root);
e[root].push_back(ls);
ls = now;
}
}
void sol(int i)
{
mx = 0;
root = 0;
dfs(i,i);
dfs2(root,root,1);
}
void solve()
{ ls = -1;
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
e[i].clear();
dep[i].clear();
vis[i] = 0;
}
if(n==3)
{
if(m==1)
{
int u,v;
cin>>u>>v;
if(u<v)
swap(u,v);
// u>v
int a = (u==3?(v==2?1:2):3);
cout<<u<<' '<<a<<' '<<v<<endl;
return ;
}
}
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++)
{
if(e[i].size() == (n-1) )
{ cout<<"-1"<<endl;
return ;
}
}
for(int i=1;i<=n;i++)
if(!vis[i])
cal(i);
//cout<<endl;
//
// for(int i=1;i<=n;i++)
// {
// for(auto v:e[i])
// cout<<v<<' ';
// cout<<endl;
//
// }
sol(1);
for(int i=2;i<=n;i+=2)
{
for(auto c:dep[i])
{
cout<<c<<" ";
}
}
for(int i=1;i<=n;i+=2)
for(auto c:dep[i])
{
cout<<c<<' ';
}
cout<<endl;
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}
I-Intersecting Intervals_2024牛客暑期多校训练营6 (nowcoder.com)
该题其实看的时候,会发现无后效性,而且对于某一行,他仅仅和上一行有关系,就很容易联想到 动态规划,那么如何设立状态呢?
我们会发现每一个
(
i
,
j
)
(i,j)
(i,j) 位置其实仅和他上一个
(
i
−
1
,
j
)
(i-1,j)
(i−1,j) 相交的区间有关,显然但是对于当前点,我们比可能去枚举上一层的任意区间,这样显然一个转移都
O
(
m
2
)
O(m^2)
O(m2) 会 TLE。那么如何转移呢。其实我们可以强制一下,我们选当前点其实不关注于上面是哪一段区间,其实只关注于上面一个区间是否包含我正上方的那个点。那我们可以定义
d
p
i
j
dp_{ij}
dpij 表示为第
i
i
i 行第
j
j
j 列我们强制选时候可以达到的
∑
r
o
w
=
1
i
∑
c
o
l
=
1
j
A
r
o
w
,
c
o
l
\sum_{row=1}^i \sum _{col = 1} ^j A_{row,col}
∑row=1i∑col=1jArow,col 的最大值 .
然后如何转移呢?
我们肯定要选看包含当前点的区间最大值为多少,这个只要暴力维护前缀和
p
r
e
j
pre_j
prej max 和后缀和
p
o
s
j
pos_j
posj max 然后转移
d
p
[
i
]
[
j
]
=
m
a
x
(
∑
k
=
1
j
d
p
[
i
−
1
]
[
k
]
+
s
u
m
[
k
.
.
j
]
+
p
r
e
k
+
p
o
s
j
,
∑
k
=
j
+
1
n
d
p
[
i
−
1
]
[
k
]
+
s
u
m
[
j
.
.
.
k
]
+
p
r
e
j
+
p
o
s
k
)
dp[i][j] =max( \sum_{k=1}^j dp[i-1][k] + sum[k..j] +pre_k + pos_j,\\ \sum_{k=j+1}^n dp[i-1][k] + sum[j...k] +pre_j + pos_k)
dp[i][j]=max(k=1∑jdp[i−1][k]+sum[k..j]+prek+posj,k=j+1∑ndp[i−1][k]+sum[j...k]+prej+posk)
求和符号我们可以维护前后缀最大值来解决。
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n,m;
cin>>n>>m;
vector<vector<int> > a(n+1,vector<int> (m+1)),dp(n+1,vector<int> (m+1));
// dp[i][j] 表示在 Row i 处选定 j 的最大区间和。
// 那么我们转移就是 dp[i][j] = \sum_{k=1}^j dp[i-1][k] + sum[k..j] +pre_k + pos_j
// 以及从后往前的 dp[i][j] = \sum_{k=j+1}^n dp[i-1][k] + sum[j...k] +pre_j + pos_k
// 我们可以维护 dp[i-1][k] + a[k] 这样的max
//对于后面两个式子我们可以用顺着加 和逆着加 即可
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
{ vector<int> pre(m+2);
vector<int> pos(m+2);
vector<int> dpre(m+2);
vector<int> dpos(m+2);
for(int j=1;j<=m;j++)
{
pre[j] += pre[j-1] + a[i][j];
pre[j] = max(pre[j],a[i][j]);
}
for(int j=m;j>=1;j--)
{
pos[j] += pos[j+1] + a[i][j];
pos[j] = max(pos[j],a[i][j]);
}
dpre[0] = -1e18;
dpos[m+1] = -1e18;
for(int j=1;j<=m;j++)
{
dpre[j] = max(dpre[j-1]+a[i][j],dp[i-1][j] + pre[j]);
dp[i][j] = dpre[j] + pos[j] - a[i][j];
}
for(int j=m;j>=1;j--)
{
dpos[j] = max(dpos[j+1] + a[i][j],dp[i-1][j] + pos[j]);
dp[i][j] = max(dpos[j] + pre[j] - a[i][j],dp[i][j]);
}
// cout<<"case "<<i<<':'<<endl;
// for(int j=1;j<=m;j++)
// cout<<pre[j]<<' ';
// cout<<endl;
// for(int j=1;j<=m;j++)
// cout<<pos[j]<<' ';
// cout<<endl;
}
// for(int i=1;i<=n;i++)
// { for(int j=1;j<=m;j++)
// cout<<dp[i][j]<<' ';
// cout<<endl;
//
// }
int ans = -LLONG_MAX;
for(int i=1;i<=m;i++)
{
ans = max(dp[n][i],ans);
}
cout<<ans<<endl;
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}
J-Stone Merging_2024牛客暑期多校训练营6 (nowcoder.com)
对于不成立情况,其实很显然就是 2 → k 2 \rightarrow k 2→k 个数字都在石头上出现,否则就会有答案。(但是如果仅有 2 2 2 个 2 2 2 是可以的,需要特判)
剩下的就是合法方案,但是如何去构造?
我们假设我们找到其中一个不存在的数字为 x x x ,那么我们其实是想把我们的堆的个数降到 x x x 这样一次就可以实现。那么我们看一下我们需要合并多少石头呢?
r e s = ( n − 1 ) % ( x − 1 ) + 1 res = (n-1)\%(x-1) +1 res=(n−1)%(x−1)+1 ,即除了最后的一个我们要保留的,剩下 n − 1 n-1 n−1 个石头看每次用 x x x 合并其实等价于每次减少 x − 1 x-1 x−1 ,因为合成的石头也算一个。
然后我们看这样 n − 1 n-1 n−1 需要减去多少才能被整除,即一次消去。
此时我们可能想从后面往前面暴力,然后找到某个可以使用的机器,然后使用。但是其实没有这么麻烦。
我们可以想一下我们在学 欧几里得的时候,是不是每次取余其实都是减去原来的一半以上,那么其实对于当前的 r e s res res 由抽屉原理可以知道要么有 r e s res res 个相同的,要么有 r e s res res 个不同的。那么我们就可以使用 r e s res res 机器来处理即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n,k;
cin>>n>>k;
vector<int> a(n+1);
vector<int> ton(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
ton[a[i]] ++;
}
if(n==2 && ton[2] == 2)
{
cout<<1<<endl;
cout<<n<<' ';
for(int i=1;i<=n;i++)
{
cout<<i<<' ';
}
cout<<endl;
return ;
}
int x = 0;
for(int i=2;i<=k;i++)
{
if(!ton[i])
{
x = i;
break;
}
}
if(!x)
{
cout<<"-1"<<endl;
return ;
}
int res = (n-1) %(x-1) +1;
vector<int> sto ;
vector<int> id(4*n+10,0);
for(int i=1;i<=n;i++)
id[i] = 1;
int t = (n-1)/(x-1);
//cout<<n-1 + x-2<<' '<<x<<endl;
int len = n;
if(res == 1)
{
;
}
else if(ton[res] >= res )
{
len+=2;
id[len-1] = 1;
for(int i=1;i<=n;i++)
{
if(a[i]==res && sto.size() < res)
{
sto.push_back(i);
id[i] = 0;
}
}
}
else
{
len+=2;
id[len] = 1;
for(int i=1;i<=n;i++)
{
if(a[i]!=res && sto.size() < res)
{
sto.push_back(i);
id[i] = 0;
}
}
}
//cout<<"case"<<res<<endl;
cout<<t + (sto.size()==0?0:1)<<endl;
if(sto.size()){
cout<<sto.size()<<' ';
for(auto c:sto)
cout<<c<<' ';
cout<<endl;
}
int l = 1;
for(int i=1;i<t;i++)
{
len +=2;
id[len] = 1;
}
// for(int i=1;i<=len;i++)
// cout<<id[i]<<' ';
// cout<<endl;
//
//cout<<len<<endl;
while(l<=len-2 )
{ int num = x;
cout<<x<<' ';
while(num)
{ //cout<<l<<' '<<a[l]<<endl;
if(!id[l])
;
else
{ cout<<l<<' ';
num--;
}
l++;
}
cout<<endl;
}
}
signed main (){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin>>t;
while(t--)
solve();
}