牛客2025多校 R4
牛客2025多校 R4
F:
题目大意:
void solve(){
LL n,k,c;
cin>>n>>k>>c;
vector<LL> a(n);
for (int i=0;i<n;i++) cin>>a[i];
for (int i=0;i<n;i++) a[i]=a[i]-c*i;
sort(a.begin(),a.end(),[](int x,int y){return x>y;});
LL sum=0;
for (int i=0;i<k;i++) sum+=a[i];
cout<<sum+c*k*(k-1)/2<<endl;
}
显然有个贪心的思维是如果有 \(k\) 个被选出来的数,他们在交换操作的前后相对位置不改变
所以考虑先将所有数都交换至第一个位置上的权值,从大到小排序后取前 \(k\) 个数,最后减去这 \(k\) 个数依次排开的贡献
B:
题目大意:
int n,m,k;
bool ans;
vector<vector<char>> g;
vector<vector<int>> val;
vector<vector<bool>> vis;
vector<vector<bool>> st;
void dfs1(int x,int y){
if (x<1||y<1||x>n||y>m) return;
if (g[x][y]=='1') return;
if (vis[x][y]) return;
vis[x][y]=1;
dfs1(x+1,y);
dfs1(x-1,y);
dfs1(x,y-1);
}
void dfs2(int x,int y){
if (x<1||y<1||x>n||y>m) return;
if (g[x][y]=='1') return;
if (st[x][y]) return;
st[x][y]=1;
if (vis[x][y]==0) ans=1;
if (val[x][y+1]>=k+y) dfs2(x,y+1);
dfs2(x+1,y);
dfs2(x-1,y);
}
void solve(){
cin>>n>>m>>k;
g.assign(n+1,vector<char>(m+1));
val.assign(n+1,vector<int>(m+1,0));
vis.assign(n+1,vector<bool>(m+1,0));
st.assign(n+1,vector<bool>(m+1,0));
ans=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
cin>>g[i][j];
for (int j=m;j>=1;j--){
for (int i=1;i<=n;i++)
if (g[i][j]=='0') val[i][j]=max(val[i][j],j);
for (int i=1;i<n;i++)
if (g[i][j]=='0'&&g[i+1][j]=='0') val[i+1][j]=max(val[i][j],val[i+1][j]);
for (int i=n-1;i>=1;i--)
if (g[i][j]=='0'&&g[i+1][j]=='0') val[i][j]=max(val[i][j],val[i+1][j]);
if (j>1) for (int i=1;i<=n;i++)
if (g[i][j]=='0'&&g[i][j-1]=='0') val[i][j-1]=val[i][j];
}
dfs1(1,m);
dfs2(1,1);
if (ans) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
val[i][j]
表示在 \(i,j\) 的位置上可以看到的最右边的列坐标,因为可以自由上下走,所以还需要对当前的第 \(j\) 列都维护出来
反向 DFS 一次,把所有可以到终点的路径都标记下来。最后再正向 DFS,模拟马里奥行走的模式
当在 \(i,j\) 的位置上能向右走时,当且仅当下一步的 val[x][y+1]
的视野内仍无法判断是否在死胡同上
如果在死胡同上(vis[x][y]==0
),那么就存在这样的一个满足要求的路径
G:
题目大意:
const int mod=998244353;
LL ksm(LL a,LL b,LL p){
LL res=1;
while (b){
if (b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
void solve(){
string s;
cin>>s;
int n=s.size();
vector<int> a(n+1);
for (int i=0;i<n;i++)
a[i+1]=(s[i]=='('?1:-1);
vector<int> pre(n+1);
for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
vector<int> lt(n+2);
for (int i=1;i<=n;i++) lt[i]=lt[i-1]+(s[i-1]=='(');
vector<int> rt(n+2);
for (int i=n;i>=1;i--) rt[i]=rt[i+1]+(s[i-1]==')');
int p=1;
LL ans=0;
for (int i=1;i<=n;i++){
p=max(p,i);
while (p<=n&&pre[p]>1) p++;
if (s[i-1]=='(')
ans=(ans+ksm(2,lt[i-1],mod)*ksm(2,rt[p+1],mod)%mod)%mod;
}
ans=(ans+ksm(2,n/2,mod))%mod;
cout<<ans*ksm(ksm(2,n,mod),mod-2,mod)%mod<<endl;
}
如果当前的 '?' 只能被解读为 '(' ,那么说明该重建唯一,考虑一个合法的前缀串的性质是 '(' 的数量大于等于 ')' 的数量
将 '(' 视为 \(1\) ,')' 视为 \(-1\) ,那么一个合法的前缀串当且仅当前缀和大于等于 \(0\)
对于每个 '(' ,我都想知道将它变成 ')' 后是否合法,如果不合法,那么重建唯一
所以找一段连续的子串,当这一段子串的部分和为 \(0\) 时,说明其中的 '(' 不能被替换,这就是一种可能的方案
这个子串左边的部分和右边的部分相对于子串独立,即子串左边的 '(' 和子串右边的 ')' 不影响子串内方案的唯一性
for (int i=1;i<=n;i++){
p=max(p,i);
while (p<=n&&pre[p]>1) p++;//找满足条件的右边界
if (s[i-1]=='(')
ans=(ans+ksm(2,lt[i-1],mod)*ksm(2,rt[p+1],mod)%mod)%mod;
}
类似于双指针的性质,p
指针和遍历字符串的 i
指针都只会遍历字符串一次,时间复杂度是线性的
I:
题目大意:
int n,m;
vector<vector<char>> g;
int scc[60][60],idx;
int box[3000],des[3000];
int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};
pair<int,int> pre[60][60];
vector<pair<int,int>> path;
vector<pair<pair<int,int>,pair<int,int>>> ans;
void bfs(int sx,int sy,int num){
queue<pair<int,int>> q;
q.push({sx,sy});
scc[sx][sy]=num;
while (q.size()){
auto t=q.front();
q.pop();
if (g[t.first][t.second]=='@') box[num]++;
if (g[t.first][t.second]=='*') des[num]++;
if (g[t.first][t.second]=='!') box[num]++,des[num]++;
for (int i=0;i<4;i++){
int tx=t.first+dx[i],ty=t.second+dy[i];
if (tx<1||ty<1||tx>n||ty>m) continue;
if (g[tx][ty]=='#') continue;
if (scc[tx][ty]) continue;
scc[tx][ty]=num;
q.push({tx,ty});
}
}
}
void bfs2(int sx,int sy){
queue<pair<int,int>> q;
q.push({sx,sy});
vector<vector<bool>> vis(n+1,vector<bool>(m+1,0));
vis[sx][sy]=1;
while (q.size()){
auto t=q.front();
q.pop();
if (g[t.first][t.second]=='*'){
int x=t.first,y=t.second;
do{
path.push_back({x,y});
int tx=x,ty=y;
x=pre[tx][ty].first,y=pre[tx][ty].second;
}while (x!=sx||y!=sy);
path.push_back({sx,sy});
return;
}
for (int i=0;i<4;i++){
int tx=t.first+dx[i],ty=t.second+dy[i];
if (tx<1||ty<1||tx>n||ty>m) continue;
if (g[tx][ty]=='#') continue;
if (vis[tx][ty]) continue;
vis[tx][ty]=1;
pre[tx][ty]={t.first,t.second};
q.push({tx,ty});
}
}
}
void makepath(vector<pair<int,int>> &path){
int p=0;
for (int i=0;i<path.size();i++){
if (g[path[i].first][path[i].second]!='.'){
for (int j=i;j>p;j--)
ans.push_back({path[j],path[j-1]});
p=i;
}
}
g[path.back().first][path.back().second]='.';
g[path.front().first][path.front().second]='!';
}
void solve(){
cin>>n>>m;
g.assign(n+1,vector<char>(m+1));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
cin>>g[i][j];
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (g[i][j]!='#'&&scc[i][j]==0)
bfs(i,j,++idx);
for (int i=1;i<=idx;i++){
if (box[i]!=des[i]){
cout<<-1;
return;
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++){
if (g[i][j]=='@'){
path.clear();
bfs2(i,j);
makepath(path);
}
}
}
auto getpos=[](pair<int,int> a,pair<int,int> b){
int dx=b.first-a.first;
int dy=b.second-a.second;
if (dx>0) return 'D';
else if (dx<0) return 'U';
else if (dy>0) return 'R';
else return 'L';
};
cout<<ans.size()<<endl;
for (int i=0;i<ans.size();i++)
cout<<ans[i].first.first<<' '<<ans[i].first.second<<' '<<getpos(ans[i].first,ans[i].second)<<'\n';
}
墙壁将图分割为有限个连通块,当且仅当每个连通块内箱子数和终点数相同时才会有解
所以先 BFS 一次搜索所有连通块,判断是否有解
然后是对路径的维护,考虑这样一个问题:一个箱子移动到终点的路径上如果有其他箱子阻挡该怎么办
事实上可以看作交换这两个箱子,即形式化的在箱子 \(a_1\) 移动到终点的过程中会被 \(a_2,a_3,\cdots,a_i\) 这些箱子阻挡
那么可以先将 \(a_i\) 移动到终点,\(a_{i-1}\) 移动到 \(a_i\) ,这样操作下来可以视作将 \(a_1\) 移动到了终点
最后在对每个箱子都做一遍 BFS 找到距离最近的终点,然后利用前导数组记录下移动的路径
最后模拟一次将 \(a_i\) 移动到终点,\(a_{i-1}\) 移动到 \(a_i\) 的过程即可
int p=0;
for (int i=0;i<path.size();i++){
if (g[path[i].first][path[i].second]!='.'){//如果遇到了拦路的箱子
for (int j=i;j>p;j--)
ans.push_back({path[j],path[j-1]});//先将这个箱子移动走
p=i;
}
}
D:
题目大意:
int g[201][201];
void solve(){
int N;
cin>>N;
int n=200;
for (int i=1;i<=n;i++) g[i][i]=1;
g[n][n]=N;
for (int i=n-1,v=1;i>=1;i-=2,v^=1){
if (N&1){
if (v){
g[i][n]=g[i-1][n]=-(N>>1);
N>>=1;
}else{
g[i][n]=g[i-1][n]=(N+1)>>1;
N=(N+1)>>1;
}
}else{
if (v){
g[i][n]=g[i-1][n]=-(N>>1);
N>>=1;
}else{
g[i][n]=g[i-1][n]=(N>>1);
N>>=1;
}
}
if (N==0) break;
}
for (int i=n,v=1;i>=1;i--,v++){
if (g[i][n]==0) break;
if (v&1) g[i][i-1]=g[i][i-2]=1;
else g[i][i-2]=g[i][i-3]=1;
g[i][n]&=1;
}
cout<<n<<endl;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
cout<<g[i][j]<<' ';
cout<<endl;
}
}
显然这样的一个矩阵的行列式为 \(N\)
因为需要构造的是一个 01 矩阵,所以考虑行变换将 \(N\) 用 01 表示
当 \(N\) 为正整数且是奇数时:\(N-2\lfloor \frac{N}{2}\rfloor=1\),如果为正偶数,有:\(N-2\lfloor \frac{N}{2}\rfloor=0\)
当 \(N\) 为负整数且是奇数时:\(N-2\lceil \frac{N}{2}\rceil=1\) ,如果为负偶数,有:\(N-2\lceil \frac{N}{2}\rceil=0\)
那么对于第 \(n\) 列,我们可以将 \(N\) 分解到每一行上,形如:
即对于第 \(j\) 行,总是可以由 \(j-1,j-2\) 这两行通过行变化使得 \(A_{j,n}\) 为 \(0,1\)