The 2022 ICPC Asia Nanjing Regional Contest
A.Stop, Yesterday Please No More
思路:因为袋鼠是同时移动的,所以我们可以不考虑袋鼠怎么动,而去考虑边界怎么动。所以我们先不考虑洞的影响,先确定哪些会因为边界而离开。确定好最终边界,再进行一次模拟,加入有洞的情况,发现洞产生的路径都可以通过平移获得,那么就只维护一条路径.
然后我们来统计这个小矩形里活的袋鼠经过在移动过程中经过的格子,也就是统计m*n个格子中每个经过了这个小矩形中多少只袋鼠(注意这个洞可以在m*n的任意位置,而并非只是这个小矩形)然后计算经过数目为(x-k)的格子的数目。我们通过模拟剩余的袋鼠的矩阵,每次将这个矩阵范围的格子++(记得初始矩阵也要统计),由于矩阵的大小已经是唯一确定的了,我们只需要开一个二维布尔数组统计左上(或右下)是否已经计过数,就可以很轻松的判重了,最后再用二维前缀和统计出答案输出即可。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n,m,k;
string s;
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int t;
cin>>t;
while(t--)
{
cin>>n>>m>>k;
cin>>s;
int sz = s.size();
s = "?"+s;
vector<vector<int>>mp(n+10,vector<int>(m+10));
vector<vector<int>>vis(n+10,vector<int>(m+10));
//最大偏移量,注意我们移动的是图,和袋鼠移动方向相反
int L = 1,R = m;
int U = 1,D = n;
int curL = 1,curR = m;
int curU = 1,curD = n;
for(int i = 1;i <= sz; i++)
{
if(s[i]=='L')curL++,curR++;
else if(s[i]=='R')curL--,curR--;
else if(s[i]=='U')curU++,curD++;
else if(s[i]=='D')curU--,curD--;
L = max(L,curL);
R = min(R,curR);
U = max(U,curU);
D = min(D,curD);
}
if(L>R||U>D){
if(k==0)cout<<n*m<<"\n";
else cout<<0<<"\n";
continue;
}
int x1 = U,y1 = L;
int x2 = D,y2 = R;
auto add = [&](int x1,int y1,int x2,int y2){
if(!vis[x1][y1]){
vis[x1][y1] = 1;
mp[x1][y1]++;
mp[x2+1][y1]--;
mp[x1][y2+1]--;
mp[x2+1][y2+1]++;
}
};
add(x1,y1,x2,y2);
//反向移动该小矩形。做差分,然后前缀和处理每个点能经过多少袋鼠。
for(int i = 1;i <= sz; i++){
if(s[i]=='L')y1--,y2--;
else if(s[i]=='R')y1++,y2++;
else if(s[i]=='U')x1--,x2--;
else if(s[i]=='D')x1++,x2++;
add(x1,y1,x2,y2);
}
for(int i = 1;i <= n; i++){
for(int j = 1;j <= m; j++){
mp[i][j] += mp[i-1][j]+mp[i][j-1]-mp[i-1][j-1];
}
}
int cnt = (D-U+1)*(R-L+1),ans = 0;
int need = cnt-k;
if(need<0){
cout<<0<<"\n";
continue;
}
for(int i = 1;i <= n; i++){
for(int j = 1;j <= m; j++){
if(need==mp[i][j])ans++;
}
}
cout<<ans<<"\n";
}
return 0;
}
D. Chat Program
思路:二分+差分
二分第\(k\)大,第\(k\)大的数\(\ge mid\) 等价于 有超过\(k\)个\(\ge mid\)
对于加上一段等差数列有什么影响呢?一个数刚被等差数列右端点经过时候是最大的,之后慢慢变小,最后恢复成本身。我们可以确定当加上一个等差数列,每个数的贡献范围(用差分处理),最后再做一次前缀和操作即可。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll n,k,m,c,d,a[N];
int f[N];
bool judge(ll x)
{
memset(f,0,sizeof(f));
int cnt = 0;
for(int i = 1;i <= n; i++)
cnt += (a[i]>=x);
if(cnt>=k)return true;
for(int i = 1;i <= n; i++)if(a[i]<x){
ll l = max(1ll,i-m+1);
ll mx = a[i]+c+(i-l)*d;
if(mx<x)continue;
ll mn = a[i]+c;
ll pos;
if(d == 0)pos = 0;
else
pos = (x-mn-1)/d+1;
pos = max(pos,0ll);
int r = i-pos;
f[l]++;
f[r+1]--;
}
int ans = 0;
for(int i = 1;i <= n; i++)
{
f[i] += f[i-1];
ans = max(ans,f[i]);
}
return ans+cnt>=k;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>k>>m>>c>>d;
for(int i = 1;i <= n; i++)
cin>>a[i];
ll l = 0,r = 1e18;
while(l<=r)
{
ll mid = (l+r)>>1;
if(judge(mid))l = mid+1;
else r = mid-1;
}
cout<<l-1<<"\n";
return 0;
}
G. Inscryption
思路:反悔贪心。题目中\(0\)可以变成\(1\)也可以变成\(2\)。当然对于我们来说,操作\(2\)比操作\(1\)更优,我们尽量执行操作\(2\)。但是,如果变成\(2\)导致之后的决策无非进行,那么我们需要一个反悔,把之前的操作\(0\)改成操作\(1\)。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
int a[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i = 1; i <= n; i++)
cin>>a[i];
int sum = 1,cnt = 1,use = 0;
//sum是当前总战斗力,cnt是当前怪兽个数,use是0变成2的操作次数
bool ok = true;
for(int i = 1; i <= n; i++)
{
if(a[i]==1)sum++,cnt++;
else if(a[i]==-1)
{
if(cnt>=2)cnt--;
else if(use>=1)use--,cnt++,cnt++,sum++,cnt--;
else{
ok = false;
break;
}
}
else{
if(cnt>=2)cnt--,use++;
else sum++,cnt++;
}
}
// cout<<"cnt = "<<cnt<<" sum = "<<sum<<"\n";
if(!ok)cout<<-1<<'\n';
else{
int g = __gcd(sum,cnt);
cout<<sum/g<<" "<<cnt/g<<"\n";
}
}
return 0;
}