I.矩阵

数学 #贪心 #构造

题目

思路

首先考虑有数最受条件的约束,因此尝试令数\(x\)沿着某方向前进\(x\)后回到原地:

\[\begin{align}
(x+x+1)\%n-1&=x\\ \\
(2x+1)\%n&=x+1\\ \\
2x+1&\equiv x+1\mod n\\ \\
x&\equiv 0\mod n
\end{align}
\]

则有\(x\)为\(n\)的因数

因此,当\(x\)为\(n\)的因数时,\(x\)无法在\(n\)方向上进行移动,\(m\)方向同理

因此,\(x=lcm(n,m)\)时,\(x\)一定无法移动

因此\(lcm(n,m)\)必须为最后一个填入的数字,可以利用这一点进行\(YES/NO\)的判断

接下来通过观察贪心地进行填空:

  • 尝试将一列填满后再填下一列
  • 尝试每填完一个数就变换一次移动方向,比如这次在列方向上向下移动,下一次列方向上的移动就向上,这样可以保证移动后不会踩到已经被填过的格子

代码实现

代码由\(phaethon 90\)书写

#include<iostream>
#include<vector>
using namespace std;
#define ll long long
#define endl '\n' ll gcd(ll a,ll b)
{
if(b==0) return a;
else return gcd(b,a%b);
}
void eachT()
{
ll n,m;
cin>>n>>m;
if(n/gcd(n,m)*m<n*m)
{
cout<<"NO\n";
return;
} vector<vector<int>>mp(n+1,vector<int>(m+1,0));
int i=0,j=0,cnt=0;
int dirn=1,dirm=1;
mp[0][0] = ++cnt;
while(cnt<n*m)
{
if(cnt%n==0)
{
j = (j+cnt%m*dirm+m*m)%m;
mp[i][j] = ++cnt;
dirm *= -1;
}
else
{
i = (i+cnt%n*dirn+n*n)%n;
mp[i][j] = ++cnt;
dirn *= -1;
}
}
cout<<"YES\n";
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cout<<mp[i][j]<<' ';
}
cout<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0); int t=1;
// cin>>t;
while(t--) eachT();
return 0;
}

E.老师与好感度

dp #线性dp

题目

思路

由于\(0\leq a_{i}\leq 100\),很容易想到要枚举最后出现的\(m\)个数,考虑\(m=2\)的情况,即枚举两个目标\(tar_{1},tar_{2}\)(\(target\))

状态表示:

\(dp[i][j]\)表示从\(1\)遍历到\(i\),第\(i\)个学生的好感度变为\(tar_{j}\)(\(1\leq j\leq 2\))的最小总天数

状态转移:

\[\begin{align}
&dp[i][j]=\min_{0\leq k\leq[m=2]}\{dp[i][j]\ , \ dp[i-1][k]+ \max\{ tar_{j}-a_{i}-(tar_{k}-a_{i-1})\ ,\ 0 \} \},0\leq j\leq[m=2]\\ \\
&if(tar_{j}-a_{i}<0)dp[i][j]=inf
\end{align}
\]

当\(m=2\)的时候,\(dp[i][j]\)将由\(i-1\)时的两个状态转移过来,分别是选\(tar_{1},tar_{2}\)的状态

要与\(0\)取\(max\)是因为\(a_{i}\)与\(tar\)的差距可能比较小,仅用之前的天数就可以达到目标

枚举\(tar_{1},tar_{2}\),每次都取全局最小值即可

注意,\(tar_{1}\)的上下界分别为\(a_{i}\)的最大值与最小值,\(tar_{2}\)的上下界分别为\(200\)与\(tar_{1}\)

\(tar_{2}\)的上界不是\(a_{i}\)的最大值,因为可能会有区间覆盖在\(a_{i}\)最大值上使得它变大

最坏情况下,\(a_{i}\)的最大值被覆盖的次数量级为\(o(n)\),因此上界设为\(100+100\)即可

代码实现

#include<iostream>
#include<vector>
#include<map>
#include<cmath>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
constexpr int inf = 1e9 + 5;
// #define int ll const int up=155;
void chmin(int&x,int y){
x=min(x,y);
}
void chmax(int&x,int y){
x=max(x,y);
} const int N=405;
int a[N],dp[N][2],t[2]; void eachT() {
int n,m;cin>>n>>m;
int ma=0,mi=inf;
rep(i,1,n){
cin>>a[i];
chmax(ma,a[i]);
chmin(mi,a[i]);
dp[i][0]=dp[i][1]=inf;
}
int ans=inf;
rep(tar1,mi,ma){
rep(tar2,tar1,up){
t[0]=tar1,t[1]=tar2;
dp[1][0]=t[0]-a[1];
dp[1][1]=(m==2)?t[1]-a[1]:inf;
if(dp[1][0]<0)dp[1][0]=inf;
if(dp[1][1]<0)dp[1][1]=inf;
rep(i,2,n){
rep(j,0,(m==2)){
rep(k,0,(m==2)){
chmin(dp[i][j],max(t[j]-a[i]-(t[k]-a[i-1]),0)+dp[i-1][k]);
if(t[j]-a[i]<0)dp[i][j]=inf;
}
}
}
chmin(ans,min(dp[n][0],dp[n][1]));
rep(i,2,n)dp[i][0]=dp[i][1]=inf;
}
}
cout<<ans<<'\n';
} signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t = 1;
cin >> t;
while (t--) {
eachT();
}
}

F. 老师和 Yuuka 逛商场

线段树 #线段树二分

题目

思路

由于要确定两个隔板,非常容易想到的一个思路便是\(o(n)\)遍历左隔板,\(o(\log n)\)查找最优右隔板,总复杂度\(o(n\log n)\)

设集合\(S_{l}\)表示区间\([1,l]\)内的元素(去重),那么\([l+1,n]\)内能对答案有贡献的数字必然要属于\(S_{l}\),因此我们可以将\([l+1,n]\)这一段序列视作只剩下了属于\(S_{l}\)的元素

我们将序列分为三段:\([1,l],[l+1,r-1],[r,n]\)

目标即使得\([l+1,r-1],[r,n]\)中共同元素的数量尽可能大

因此我们对每个元素在区间\([l+1,n]\)中最早出现和最晚出现的位置\(lpos,rpos\)进行维护,当指针落于元素\(x\)的\([lpos,rpos]\)中时便说明该指针的左边与右边必定都有元素\(x\)出现

每次移动\(l\)指针的时候,都使用队列\(deque\)更新元素\(a[l]\)的\(lpos,rpos\)

接着,使用线段树对区间\([lpos+1,rpos]\)进行区间\(+1\),同时维护区间的最大值

为什么左端点是\(lpos+1\)呢?

这是为了保证指针\(r\)坐落于区间中的时候,\([r,n]\)上必定有元素\(x\),\([l+1,r-1]\)上也必定有元素\(x\)

考虑边缘情况,若\(r=lpos+1\),那么\(r-1=lpos\),刚好将最左边的元素\(x\)包含进了区间中

令\(r\)指针落在区间\([l+1,n]\)的\(max\)值的点上,此时左右区间必定有\(max\)值个相同元素,此处便是最优分割处

如何在线段树上找到这个\(max\)值的位置呢?

我们可以在线段树上二分,通过\(pair\)来储存\(max\)的值与位置:

  • \(pair\!<\!int,int\!>\)类型的\(find(p,l,r)\)函数

    • 传回的\(pair\)中,\(first\)为\(max\)的值,\(second\)为\(max\)值的位置
    • 功能:查询区间\([l,r]\)内的单点最大值及其位置
    • 对比左右子树的\(max\)值,选择\(max\)值较大的子树分裂查询
  • \(pair\!<\!int,int\!>\)类型的\(query(p,l,r,ql,qr)\)函数

    • 传回的\(pair\)中,\(first\)为\(max\)的值,\(second\)为\(max\)值的位置
    • 功能:查询区间\([ql,qr]\)内的单点最大值及其位置
    • 若\([l,r]\)被\([ql,qr]\)完全覆盖,那么可以直接调用\(find(p,l,r)\)
    • 否则需要分裂查询左子树与右子树,返回左右子树中的较大者

为什么要设计两个函数来二分查找?

因为给定的\(ql,qr\)不一定是某个已知线段树节点所维护的区间\(l,r\),可能需要分裂成多个区间进行比较

通过\(query\)函数将\([ql,qr]\)分裂成一个个已知区间\([l,r]\),再通过\(find\)函数在线段树上查找

每次更新\(l\)指针时,先删去\(a[l]\)的\([lpos+1,rpos]\),\(a[l]\)的\(deque\)进行\(pop\_front\)之后再加回\([lpos'+1,rpos]\)

调用\(query(1,1,n,l+2,n)\)查询区间\([l+2,n]\)中的最大值与位置以确定最优的\(r\)指针

之所以是\(l+2\)的原因是,当\(r\)取\(l+2\)的时候,\(r-1\)为\(l+1\),与之前讨论的边界情况相符

注意\(l,r\)分别初始化为\(2,3\),以防\(n=3\)的情况

一直取全局最大值即可

代码实现

#include<iostream>
#include<vector>
#include<cmath>
#include<queue>
#include<unordered_set>
#include<unordered_map>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
constexpr int inf = 1e9;
#define int ll
// #define double long double const int N = 1e5 + 5e4 + 5;
int n, a[N]; unordered_map<int, deque<int>>mp; #define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1) int add[N << 2], ma[N << 2];
void pushup(int p) {
ma[p] = max(ma[ls], ma[rs]);
}
void pushdown(int p, int l, int r) {
if (add[p]) {
ma[ls] += add[p];
ma[rs] += add[p];
add[ls] += add[p];
add[rs] += add[p];
add[p] = 0;
}
}
void modify(int p, int l, int r, int x, int y, int val) {
if (x <= l && r <= y) { ma[p] += val; add[p] += val; return; }
pushdown(p, l, r);
if (x <= mid)modify(ls, l, mid, x, y, val);
if (y > mid)modify(rs, mid + 1, r, x, y, val);
pushup(p);
} pair<int, int> find(int p, int l, int r) {
if (l == r)return { ma[p],l };
pushdown(p, l, r);
if (ma[ls] >= ma[rs])return find(ls, l, mid);
else return find(rs, mid + 1, r);
} pair<int, int> query(int p, int l, int r, int ql, int qr) {
if (ql > r || qr < l) return { -1,-1 };
if (ql <= l && r <= qr) return find(p, l, r);
pushdown(p, l, r);
auto left = query(ls, l, mid, ql, qr);
auto right = query(rs, mid + 1, r, ql, qr);
return left.first >= right.first ? left : right;
} void build(int p, int l, int r) {
ma[p] = add[p] = 0;
if (l == r)return;
build(ls, l, mid); build(rs, mid + 1, r);
} void eachT() {
cin >> n; mp.clear();
build(1, 1, n); unordered_multiset<int>sl;
rep(i, 1, n) {
cin >> a[i];
mp[a[i]].push_back(i);
}
int ans = 0, ansl = 2, ansr = 3;
rep(l, 1, n) {
if (!sl.count(a[l])) {
mp[a[l]].pop_front();
int posl = mp[a[l]].front(), posr = mp[a[l]].back();
if (posl + 1 <= posr)modify(1, 1, n, posl + 1, posr, 1);
} else {
int posl = mp[a[l]].front(), posr = mp[a[l]].back();
if (posl + 1 <= posr)modify(1, 1, n, posl + 1, posr, -1);
mp[a[l]].pop_front();
if (mp[a[l]].size() >= 2) {
int posl = mp[a[l]].front(), posr = mp[a[l]].back();
if (posl + 1 <= posr)modify(1, 1, n, posl + 1, posr, 1);
}
}
sl.insert(a[l]);
pair<int, int> now = query(1, 1, n, l + 2, n);
if (ans < now.first) {
ans = now.first;
ansl = l + 1, ansr = now.second;
}
}
cout << ans << '\n' << ansl << " " << ansr << '\n';
} signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t = 1;
cin >> t;
while (t--) {
eachT();
}
}

K. 神奇集合

强联通分量 #树上背包 #dp

题目

思路

注意到题目中添加的新边为返祖边,一旦选择了该连通块中的任意一个点,那么这整个连通块都必须要选择,因此考虑对图进行\(tarjan\)缩点

缩点过程中,将新点的权值、入度进行记录

由于原图是树,则添加返祖边缩点后仍然是一棵树

建好新图后遍历所有新节点,找出入度为0的点记为根节点

从根节点进入,\(dfs\)过程中进行树上背包dp:

状态表示:

\(dp[u][j]=1 /0\)代表以\(u\)为根的子树中,是否存在总权值为\(j\)的神奇集合

状态转移:

\[\begin{align}
&sum_{u}\geq i\geq 0,sum_{son}\geq j\geq 0:dp[u][i+j]\ |=dp[u][i]\&dp[son][j]\\ \\
&dp[u][sum_{u}]=1
\end{align}
\]

从\(u\)节点已经遍历的所有可能权值中取出权值为\(i\)的情况,从\(son\)节点的所有可能权值中取出权值为\(j\)的情况,二者可以转移到\(dp[u][i+j]\)的状态中

最后考虑整个子树都选上的情况\(dp[u][sum_{u}]\)

答案即为\(\sum_{i=0}^{N} dp[root][i]\)

代码实现

#include<iostream>
#include<vector>
#include<map>
#include<cmath>
#include<set>
#include<stack>
#include<unordered_map>
using namespace std;
using ll = long long;
#define int ll
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
constexpr int inf = 1e9 + 5; // #include <ext/pb_ds/assoc_container.hpp>
// using namespace __gnu_pbds;
const int N=1e4+5;
int w[N]; struct node{
set<int>e;
int dfn,low,in,scc;
}a[N]; struct node1{
set<int>e;
int siz,deg,w,sum;
}na[N]; int tot,cnt;
stack<int>st;
void tarjan(int u){
a[u].dfn=a[u].low=++tot;
st.push(u),a[u].in=1;
for(auto&son:a[u].e){
if(!a[son].dfn){
tarjan(son);
a[u].low=min(a[u].low,a[son].low);
}else if(a[son].in){
a[u].low=min(a[u].low,a[son].low);
}
}
if(a[u].dfn==a[u].low){
int v;++cnt;
do{
v=st.top(),st.pop(),a[v].in=0;
a[v].scc=cnt,++na[cnt].siz;
na[cnt].w+=w[v];
}while(u!=v);
}
} int n;
void build(){
rep(i,1,n){
for(auto&son:a[i].e){
if(a[i].scc!=a[son].scc){
na[a[i].scc].e.insert(a[son].scc);
na[a[son].scc].deg++;
}
}
}
} bool dp[N][N]; int dfs(int u,int fa){
int sum=0;
dp[u][0]=1;
for(auto&son:na[u].e){
if(son==fa)continue;
sum+=dfs(son,u);
per(i,na[u].sum,0){
per(j,na[son].sum,0){
dp[u][i+j]|=dp[u][i]&dp[son][j];
}
}
na[u].sum=sum;
}
sum+=na[u].w;
na[u].sum=sum;
dp[u][sum]=1;
return sum;
} void eachT() {
cin>>n;
rep(i,1,n)cin>>w[i];
rep(i,1,n-1){
int u,v;cin>>u>>v;
a[u].e.insert(v);
}
int m;cin>>m;
rep(i,1,m){
int u,v;cin>>u>>v;
if(u!=v)a[u].e.insert(v);
}
rep(i,1,n){
if(!a[i].scc)tarjan(i);
}
build();
int rt;
rep(i,1,cnt){
if(na[i].deg==0)rt=i;
}
dfs(rt,0);
int cnt=0;
rep(i,0,N-1)if(dp[rt][i])cnt++;
cout<<cnt<<'\n';
} signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t = 1;
// cin >> t;
while (t--) {
eachT();
}
}

2025牛客多校第十场 K.神奇集合 F.老师和Yuuka逛商场 E.老师与好感度 I.矩阵 个人题解的更多相关文章

  1. 2020牛客多校第八场K题

    __int128(例题:2020牛客多校第八场K题) 题意: 有n道菜,第i道菜的利润为\(a_i\),且有\(b_i\)盘.你要按照下列要求给顾客上菜. 1.每位顾客至少有一道菜 2.给顾客上菜时, ...

  2. 牛客多校第十场-D- Rikka with Prefix Sum

    链接:https://www.nowcoder.com/acm/contest/148/D来源:牛客网 Prefix Sum is a useful trick in data structure p ...

  3. 牛客多校第十场 A Rikka with Lowbit 线段树

    链接:https://www.nowcoder.com/acm/contest/148/A来源:牛客网 题目描述 Today, Rikka is going to learn how to use B ...

  4. 牛客多校第十场 B Coffee Chicken 递归

    题意: 给你一个“斐波那契”字符串数列,第n项由第n-1项和第n-2项拼接而成,输出某项的某位及其后10位. 题解: 递归求解即可. #include<bits/stdc++.h> usi ...

  5. 牛客多校第十场 E Hilbert Sort 递归,排序

    题意: 给你一个方阵,再在方阵上给定一些点,按照希尔伯特曲线经过的先后顺序为这些点排序 题解: 定义好比较函数后直接调用排序算法即可. 希尔伯特曲线本来就是用于二维到一维的映射的,因此我们可以考虑对于 ...

  6. 牛客多校第十场 D Han Xin and His Troops 中国剩余定理

    题意: 韩信有若干个兵,给定你若干个模数和余数,再给你一个1e18以内的范围限制,求解同余方程组,如果无解,输出“他一定在撒谎”,如果最小解超出范围限制,输出“他可能在撒谎”,否则输出最小解 注意:不 ...

  7. 牛客多校第十场 H Stammering Chemists 判断图同构

    题意: 给出一个无向图,表示一种有机物质的结构式,问你这个有机物质是列表中的哪个. 题解: 判断图同构需要枚举全排列以对应点,但是此题中几乎只需要将点度数排序后一个一个比较,对于甲基位置再加个特判即可 ...

  8. 牛客多校第十场 F Popping Balloons 线段树维护稀疏矩阵

    题意: 给定一个稀疏矩阵,里面有若干个气球,让你横着开三枪,竖着开三枪,问最多能打爆多少气球,要求相同方向,相邻两枪必须间隔r. 题解: 横向记录每列有多少个气球,分别在哪行上. 然后把这个数据改造成 ...

  9. 2019牛客多校第四场K number dp or 思维

    number 题意 给一个数字串,问有几个子串是300的倍数 分析 dp写法:这题一看就很dp,直接一个状态dp[i][j]在第i位的时候膜300的余数是j左过去即可.这题比赛的时候样例老是少1,后面 ...

  10. 牛客多校第三场 F Planting Trees

    牛客多校第三场 F Planting Trees 题意: 求矩阵内最大值减最小值大于k的最大子矩阵的面积 题解: 矩阵压缩的技巧 因为对于我们有用的信息只有这个矩阵内的最大值和最小值 所以我们可以将一 ...

随机推荐

  1. Comparator.reverseOrder() 和 reversed()的区别

    摘要:Comparator.reverseOrder() 和 reversed()的区别是前者以某字段进行倒序排列,而reversed是针对已排序数据进行处理,常常用于比较器的末尾.   在使用Str ...

  2. 面试题:Spring BeanFactory和FactoryBean的区别

      BeanFactory:以Factory结尾,表明它是一个工厂类(接口),它是Spring IOC容器的核心接口,负责实例化和管理bean的一个工厂,为具体的IoC容器的实现提供规范.BeanFa ...

  3. AtCoder Beginner Contest 357-C

    Problem For a non-negative integer \(K\), we define a level-\(K\) carpet as follows: A level-\(0\) c ...

  4. WPF与WinForm的对比

    WPF与WinForm的对比 本文同时为b站WPF课程的笔记,相关示例代码 创建新项目 在vs2022中,这两者分别叫做WPF应用和Windows窗体应用. 渲染引擎和设计 WPF使用DirectX作 ...

  5. 「Log」2023.8.29 小记

    序幕 早上下雨了,七点到校,还是先整理博客. 今天是生日,发条犇犇纪念一下,16 岁了! 学长进行杂题选讲,一些 KD-Tree.根号分治.生成树题,大部分是图相关,高低胡上两道. 补一些题 \(\c ...

  6. SSH实现服务器之间免密登录

    1.介绍 SSH(Secure Shell)是一种用于计算机之间安全远程登录和其他网络服务的协议,它通过加密通信来确保在不安全的网络中也能安全地传输数据.SSH可以用于登录远程主机.执行命令和管理远程 ...

  7. 题解:P1032 [NOIP 2002 提高组] 字串变换

    题目链接:link. 为了高效地解决这道题目,我们考虑使用双向搜索解决. 我们从 \(A\) 和 \(B\) 同时开始搜索,要是中间有相遇,那么就说明 \(A\) 能变成 \(B\) 这样我们就结束搜 ...

  8. Es简单条件查询

    一:先看一下es的语句以及查询结果:  我这边使用的条件是is_device要么是工控要么是资产 二:java代码部分 关于es的操作,java里面不需要添加mapper层,只要在service以及c ...

  9. 生活中有苦难却没有人可以倾述?来看看AI树洞吧!

    本文由 ChatMoney团队出品 介绍说明 在如今繁忙喧嚣的世界中,我们时常渴望能有一个安全且私密的空间,让我们毫无顾忌地袒露心声.AI 智能体树洞便是这样一个独特的存在. 它并非传统意义上的树洞, ...

  10. MySQL事务:工作原理与实用指南

    MySQL事务:工作原理与实用指南 在数据库操作中,事务是保证数据一致性的重要机制.本文将深入探讨 MySQL 事务的特性.隔离级别以及实际应用场景,帮助你更好地理解和使用事务. 一.什么是事务? 事 ...