tot:这场vp写了两题罚时272,上海的场还是炸呀,差点就是我们队去了。。赛后补了3题,共5题。这场铜是从3题448到5题907,最后一银是5题888。第三题赛时的时候写复杂了一点,并且删边的部分是不对的,赛时也没有留意到。第四题没有看题解赛后补了,存在一点问题(ceil和floor精度不够--真的不要用了),然后有一个符号写错了(这个对着式子也容易找到错)。第五题一样不难,就是那种知道得想的细一点,但是又想的不够细的题,导致一直不对,然后一直对着样例改。最后对拍出样例接着改才过的(得需要两个人一起想想各种细节吧),总体真不难这题,但是也不是说很简单,feel吧。
The 2024 ICPC Asia Shanghai Regional Contest, Early Access
I. In Search of the Ultimate Artifact
思路:我实现wa了几发这个签到题,还怀疑是不是取模后最大,但是不可能过这么多人。然后yk再写了一发ac了。我的写的那发取模取少了又。。
const int mod=998244353;
const int N=1e6+10;
ll n;
ll a[N];
void solve(){
ll k; cin >> n >> k;
for(int i=1;i<=n;i++) a[i]=0;
for(int i=1;i<=n;i++){
cin >> a[i];
if(!a[i]) i--,n--;
}
if(!n){cout << "0\n"; return ;}
sort(a+1,a+n+1,greater<>());
ll ans=a[1]%mod;
for(int i=2;i<=n-k+2;i++){
for(int j=i;j<i+k-1;j++) (ans*=a[j])%=mod;
i+=k-2;
}
cout << ans << "\n";
}
C. Conquer the Multiples
思路:容易发现拿奇数的人是可以拿偶数字的,但是拿偶数字的人是不可能拿到奇数字的。那么只需要判断拿奇数字的人能不能把偶数字的人的’路‘拦截即可。
int l,r;
void solve(){
cin>>l>>r;
if(l==1||r-l+1==1){ cout<<"Alice"<<endl; return; }
if(r%2==1&&l%2==1){ cout<<"Alice"<<endl; return; }
if(r%2==1&&l%2==0){ cout<<"Bob"<<endl; return; }
if(l%2==1){
if(l*2<=r){ cout<<"Alice"<<endl; return; }
else { cout<<"Bob"<<endl; return; }
}
else{
if((l+1)*2<=r){ cout<<"Bob"<<endl; return; }
else { cout<<"Alice"<<endl; return; }
}
}
B. Basic Graph Algorithm
思路:不难的题,不能退出也不能往下一步走才建边,否则能退出就退出,细节见注释。
int n,m;
int arr[300005],nex=1;
vector<int> vct[300005];
set<int> st[300005];
vector<pair<int,int>> ans;
void dfs(int u){
//这里会有一个非常非常不好发现的错误!!如果没有vct再存一次图,只是用set存图的话.
//一旦x在y前先遍历,那么y会删除到x这条边,但是x不会删除到y这条边(因为y到x已经没边了).
//如果此时y不是被x遍历到的,那么将可能会多建边.
//即x应该要return才对的,但是x没有return,因为还存有到y的边,但是实际上y已经被遍历完了.
for(auto v:vct[u]) st[v].erase(u);
while(!st[u].empty()&&nex<=n){
if(st[u].count(arr[nex])){
st[u].erase(arr[nex]);
nex++,dfs(arr[nex-1]);
}
else{
ans.eb(u,arr[nex]);
nex++,dfs(arr[nex-1]);
}
}
}
void solve(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
//无自环,有重边.
//需要再建一个不做删减的图,用作给st删边.如果单纯用st作为删边的话,会有问题!!并且很难发现这个问题..
/*if(!st[u].count(v))*/ vct[u].eb(v),vct[v].eb(u);
st[u].ep(v),st[v].ep(u);
}
for(int i=1;i<=n;i++) cin>>arr[i];
while(nex<=n) nex++,dfs(arr[nex-1]);
cout<<sz(ans)<<endl;
for(auto a:ans) cout<<a.first<<" "<<a.second<<endl;
}
//wa on:
//10 20
//5 6
//4 6
//5 10
//5 4
//9 3
//7 10
//2 10
//3 10
//3 6
//9 7
//9 5
//4 2
//2 8
//10 2
//5 3
//4 6
//5 10
//6 7
//2 5
//8 9
//10 7 5 6 4 2 8 9 3 1
写复杂了的:
int n,m;
vector<int> vct[300005];
vector<int> arr(300005);
vector<int> pos(300005);
vector<int> cnt(300005);
vector<pair<int,int>> ans;
map<pair<int,int>,bool> mp;
vector<bool> vis(300005);
vector<pair<int,int>> itv;
vector<int> len;
int l0,r0,idx=0,cntt=0,nex=1,begin0;
void dfs0(int u){
l0=min(l0,pos[u]);
r0=max(r0,pos[u]);
vis[u]=true;
for(auto v:vct[u]){
if(!vis[v]) dfs0(v);
}
}
void dfs(int u){
cntt++;
for(auto v:vct[u]) cnt[v]++; key::当每一个点被vis,把相邻的点cnt[]++;
if(cntt==len[idx]||nex>n) return;
if(cnt[u]==sz(vct[u])&&u!=begin0) return;
while(cnt[u]<vct[u].size()||u==begin0){
if(cntt==len[idx]||nex>n) return;
int mi=min(u,arr[nex]);
int mx=max(u,arr[nex]);
if(mp[{mi,mx}]){
cnt[u]++,cnt[arr[nex]]++; 这行是不对的!!
nex++;
dfs(arr[nex-1]);
}
else{
ans.eb(u,arr[nex]);
nex++;
dfs(arr[nex-1]);
}
}
}
void solve(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v; cin>>u>>v;
vct[u].eb(v);
vct[v].eb(u);
mp[{min(u,v),max(u,v)}]=true;
}
for(int i=1;i<=n;i++){
cin>>arr[i];
pos[arr[i]]=i;
}
for(int i=1;i<=n;i++){
if(!vis[i]){
l0=INT_MAX,r0=INT_MIN;
dfs0(i);
itv.eb(l0,r0);
}
}
sort(itv.begin(),itv.end());
// for(auto i:itv) cout<<i.first<<" "<<i.second<<endl;
// cout << endl;
pair<int,int> e=itv[0];
for(int i=1;i<sz(itv);i++){
if(e.second>=itv[i].first) e.second=max(itv[i].second, e.second); key:这里得取max!!!
else len.eb(e.second-e.first+1),e=itv[i];
}
len.eb(e.second-e.first+1);
// for(auto l:len) cout<<l<<' ';
nex=1;
for(idx=0;idx<sz(len);idx++) {
cntt=0,begin0=arr[nex],nex++;
dfs(begin0);
}
cout<<sz(ans)<<endl;
for(auto a:ans) cout<<a.first<<" "<<a.second<<endl;
}
G. Geometry Task
思路:经典二分中位数,然后check是贪心的,还需要一个前缀数组和后缀数组.
按照a<0和a>0先处理出neg数组和pst数组;-用作后续!双指针!!贪心!处理出pre和suf的,主要看样例2.
c数组排序后.pre[i]定义为从1-->i最多可以匹配多少对;suf[i]定义为从n-->i最多可以匹配多少对.
然后a==0的特殊计算一下,最后枚举1-->n,取出max即可知道,在当前中位数x下,最多可以匹配多少对大于等于x的对数.
主要注意实现的细节,并且!!ceil和floor的精度是有问题的!!!不要用!!老老实实自己分类讨论一下!!
int n;
pair<int,int> line[100005];
int c[100005];
vector<int> pre(100005);
vector<int> suf(100005);
vector<int> neg,pst;
bool check(int x){ //贪心,分类讨论.
neg.clear(),neg.eb(0);
pst.clear(),pst.eb(0);
pre[0]=0,suf[n+1]=0; //init!
int cnt=0,mx=0;
for(int i=1;i<=n;i++){
int a=line[i].first,b=line[i].second;
if(a==0&&b>=x) cnt++;
else if(a>0){
// pst.eb((int)ceill((x-b)*1.0/a)); //精度有问题..非必要不用和浮点数有关的函数!!
if(x-b>=0){
if((x-b)%a==0) pst.eb((x-b)/a);
else pst.eb((x-b)/a+1);
}
else pst.eb((x-b)/a);
}
else if(a<0){
// neg.eb((int)floorl((x-b)*1.0/a));
if(x-b>=0){
if((x-b)%a==0) neg.eb((x-b)/a);
else neg.eb((x-b)/a-1);
}
else neg.eb((x-b)/a);
}
}
int n1=sz(neg)-1,n2=sz(pst)-1;
sort(neg.begin()+1,neg.begin()+n1+1);
sort(pst.begin()+1,pst.begin()+n2+1);
int idx=1;
for(int i=1;i<=n1&&idx<=n;i++){ //neg--pre
pre[idx]=pre[idx-1];
if(neg[i]==0){
if(c[idx]==0) pre[idx]++,idx++;
else if(c[idx]<0) pre[idx]++,idx++;
else if(c[idx]>0) continue;
}
else if(neg[i]<0){
if(c[idx]==0) continue;
else if(c[idx]<0){
if(abs(neg[i])<=abs(c[idx])) pre[idx]++,idx++;
}
else if(c[idx]>0) continue;
}
else if(neg[i]>0){
if(c[idx]==0) pre[idx]++,idx++;
else if(c[idx]<0) pre[idx]++,idx++;
else if(c[idx]>0){
if(neg[i]>=c[idx]) pre[idx]++,idx++; //这里是>= 不是<=
}
}
}
for(int i=idx;i<=n;i++) pre[i]=pre[i-1];
idx=n;
for(int i=n2;i>=1&&idx>=1;i--){ //pst--suf
suf[idx]=suf[idx+1];
if(pst[i]==0){
if(c[idx]==0) suf[idx]++,idx--;
else if(c[idx]<0) continue;
else if(c[idx]>0){
if(c[idx]>=pst[i]) suf[idx]++,idx--;
}
}
else if(pst[i]<0){
if(c[idx]==0) suf[idx]++,idx--;
else if(c[idx]<0){
if(pst[i]<=c[idx]) suf[idx]++,idx--;
}
else if(c[idx]>0) suf[idx]++,idx--;
}
else if(pst[i]>0){
if(c[idx]==0) continue;
else if(c[idx]<0) continue;
else if(c[idx]>0){
if(pst[i]<=c[idx]) suf[idx]++,idx--;
}
}
}
for(int i=idx;i>=1;i--) suf[i]=suf[i+1];
for(int i=1;i<=n;i++) mx=max(mx,pre[i]+suf[i]);
return cnt+mx>=(n+1)/2;
}
//经典二分中位数,然后check是贪心的,还需要一个前缀数组和后缀数组.
//按照a<0和a>0先处理出neg数组和pst数组;--用作后续!双指针!!贪心!处理出pre和suf的,主要看样例2.
//c数组排序后.pre[i]定义为从1-->i最多可以匹配多少对;suf[i]定义为从n-->i最多可以匹配多少对.
//然后a==0的特殊计算一下,最后枚举1-->n,取出max即可知道,在当前中位数x下,最多可以匹配多少对大于等于x的对数.
//主要注意实现的细节,并且!!ceil和floor的精度是有问题的!!!不要用!!老老实实自己分类讨论一下!!
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>line[i].first;
for(int i=1;i<=n;i++) cin>>line[i].second;
for(int i=1;i<=n;i++) cin>>c[i];
if(n==1){ cout<<line[1].first*c[1]+line[1].second<<endl; return; }
sort(c+1,c+n+1);
int l=-2e18,r=2e18,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<ans<<endl;
}
D. Decrease and Swap
思路:对着样例和对拍器改出来的。
合法情况,末四位如下: x,x,0,0 18,18,x,x 18,x,18,0 关键细节(按我那种实现have是判不到的,得在其中特判): 初级:010101--这种是可以的,str[4]把str[6]拖回来一次,然后str[2]把str[4]拖到str[3],str[3]再把str[5]拖到str[4]. 中级:011001--初级的变式,也是可以的. 高级:111010011100001--更是变式,也是可以的. //往右推完之后是,101010101010101. 但是在判的时候,来到str[n-4],have=3,然后去判str[i+2]和str[i+1]是否为'1',然后都不是. 然后再往后判到i==n-2:have已经不足2了,cc也为false.导致结果为No. key:但是实际上,这个推完之后是变成了初级的样子的,所以最后还要判have>=3也是合法的!!
晕头。。
int n;
char str[1000006];
//合法情况,末四位如下:
//x,x,0,0
//18,18,x,x
//18,x,18,0
关键细节(按我那种实现have是判不到的,得在其中特判):
初级:010101--这种是可以的,str[4]把str[6]拖回来一次,然后str[2]把str[4]拖到str[3],str[3]再把str[5]拖到str[4].
中级:011001--初级的变式,也是可以的.
高级:111010011100001--更是变式,也是可以的.
//往右推完之后是,101010101010101.
但是在判的时候,来到str[n-4],have=3,然后去判str[i+2]和str[i+1]是否为'1',然后都不是.
然后再往后判到i==n-2:have已经不足2了,cc也为false.导致结果为No
key:但是实际上,这个推完之后是变成了初级的样子的,所以最后还要判have>=3也是合法的!!
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>str[i];
if(str[n]=='0'&&str[n-1]=='0'){ cout<<"Yes"<<endl; return; }
if(n<=3){ cout<<"No"<<endl; return; }
if(str[n-2]=='1'&&str[n-3]=='1'){ cout<<"Yes"<<endl; return; }
if(str[n]=='0'&&str[n-3]=='1'){ cout<<"Yes"<<endl; return; }
if(str[n]=='1'&&str[n-1]=='0'){ //0,18 需要str[n-2],str[n-3]同时为18.
int have=0,cc=0;
for(int i=1;i<=n-2;i++){
if(str[i]=='1') have++;
else if(have>0) have--;
if(i==n-4&&have>0&&(str[i+2]=='1'||str[i+1]=='1'||have>=3)) cc=1; //最后加了have>=3
// if(i==n-4&&str[i]=='1'&&str[i+1]=='1') cc=1; //睡醒之后开始想能怎么wa,突然想到这个:011001--加之前wa40多,改后wa190多..
}
if(have>=2||cc) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else{ //18,0 和 18,18 只需要str[n-3]为18,str[n-2]都行.
int have=0,cc=0;
for(int i=1;i<=n-3;i++){
if(str[i]=='1') have++;
else if(have>0) have--;
if(i==n-4&&have>0&&(str[i+2]=='1'||str[i+1]=='1'||have>=3)) cc=1;
}
if(have>=1||cc) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
//没想到一种最nb的操作:10101--这个时候应该选择str[3],把最后的1换出来,再选择str[1],使得str[3]回退一步到达合适的位置.
//1
//15
//111010011100001
//细中细啊..