20240813
赛时得分
| 题目 | A | B | C | D | E | F | G | H | 总分 | 排名 | 比例 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 满分 | 1000 | 1100 | 1300 | 1900 | 1800 | 2000 | 2500 | 2700 | 14300 | 180 | 100% |
| 得分 | 1000 | 1100 | 1040 | 1382 | 338 | 600 | - | 327 | 5787 | 90 | 52.2% |
A. 比赛时间(1000/1000)
lhm 没看到无解条件,一发交上去得了 \(\text{91pts}\),是全场最快得分的人,但可惜没拿到一血。
首先判断结束时间是否在开始时间之前,如果是就输出 -1;否则天数差值 \(\times 1440\),小时差值 \(\times 60\),分钟差值不变加在一起即可。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
int a,b,c;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>a>>b>>c;
if(a<11) cout<<-1;
else if(a==11 and b<11) cout<<-1;
else if(a==11 and b==11 and c<11) cout<<-1;
else cout<<(c-11)+(b-11)*60+(a-11)*60*24;
return 0;
}
B. 可怕和弦(1100/1100)
我们尝试一种变形判断方式,我们将答案序列先排序,在将序列中得每一位减去第一位变成 \(1\) 的差值,这样就得到了一个原差的以 \(1\) 开头的有序序列,可以凭这个操作判断出来变形后的序列是否和答案序列一致。
那接下来只要一位一位枚举即可。以第 \(i\) 位开头的长度为 \(c\) 的序列是否和答案序列一致,一致就 ans++ 即可。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=2e4+1;
ll n,c,a[N],x,check[N],ans[N],cha;
bool ok;
set<ll> s;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>c;
for(int i=1;i<=c;i++) cin>>ans[i];
sort(ans+1,ans+c+1);
cha=ans[1]-1;
ans[1]=1;
for(int i=2;i<=c;i++) ans[i]-=cha;
for(int i=1;i<=n-c+1;i++)
{
memset(check,0,sizeof(check));
ok=0;
for(int j=1;j<=c;j++) check[j]=a[i+j-1];
sort(check+1,check+c+1);
cha=check[1]-1;
check[1]=1;
for(int j=2;j<=c;j++) check[j]-=cha;
for(int j=1;j<=c;j++)
{
if(check[j]!=ans[j])
{
ok=1;
break;
}
}
if(ok==0) s.insert(i);
}
cout<<s.size()<<endl;
for(auto i:s) cout<<i<<endl;
return 0;
}
C. 逃离 Lay 博士(1300/1300)
有点小难绷。lhm 赛时的时候写了个不像 dfs 的 dfs。大概是最近图上 dfs 做多了,开始忘记怎么使用非图 dfs 了。
\(\text{100%}\) 得分做法,对于我们当前要处理的下一位,我们首先判断是否发生了进位:如果发生了进位,我们有肯定不可以选;对于没有发生进位的,我们有两个选择,要么选择,要么不选。由于是都要进行的,我们先选择一下,注意方案数和总和都要加一下,等回溯回来再不选。其中方案数和总和仍旧都以传参形式存在。注意要判断边界条件,编号大于总数时直接 return 回溯。
接下来我们要考虑一下怎么判断是否合法,那么我们把两个数从个位起相应数拿出来加在一起,如果大于等于 \(10\),显然不合法。其他就是合法,直到一个数的位数被全部判断完为止。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=21;
ll n,a[N],ans=-1;
bool pd(ll x,ll y)
{
ll xx=max(x,y),yy=min(x,y);
string s1=to_string(xx),s2=to_string(yy);
ll cnt=s2.length()-1;
for(int i=s1.length()-1;i>=0;i--)
{
if(cnt<0) break;
string ins1="",ins2="";
ins1+=s1[i],ins2+=s2[cnt];
ll numx=stoi(ins1),numy=stoi(ins2);
if(numx+numy>=10) return 0;
cnt--;
}
return 1;
}
void dfs(ll now,ll x,ll cnt)
{
ans=max(ans,cnt);
if(now>n) return;
if(pd(x,a[now])==1) dfs(now+1,x+a[now],cnt+1);
dfs(now+1,x,cnt);
return;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
dfs(1,0,0);
cout<<ans;
return 0;
}
D. 奶牛的延迟计划(1900/1900)
这题赛时得了 \(\text{73%}\) 的得分,我的思路没有任何问题,可惜只有一行出了锅导致无缘破 PB。不过再怎么说这也是 lhm 第一次在比赛中图论题拿分,还是值得纪念的。
\(\text{100%}\) 得分做法,我们考虑每次必须把操作放在最短路上才能尽可能使答案增大。那么不难想到我们先跑一遍 dijkstra,求出 Lay 博士第一遍要走的最短路,并得到最短路的整条路径,然后把枚举把第 \(i\) 条路的权值翻倍建到一个新图中,然后再跑一遍 dijkstra 求差值。我们最终的答案就是这个差值的最大值。
那么做题的时候我一开始无脑选的是最短路中权值最大的那条路径,其实不然。因为最短路的部分路径可能也存在于答案路径中,最大化答案路径当然是更优秀的。那既然没有最优决策我们就直接全部模拟就好了呗。
赛时最后呢就是我在存新图的时候找操作边,我忘记了边的起点终点不一定是按照原图方向给出来的,因为是双向边。然后就把 if(min(u[i],v[i])==min(taru,tarv) and max(u[i],v[i])==max(taru,tarv)) 写成了 if(u[i]==taru and v[i]==tarv),一百多行的代码就错了这一行,丢了加权后的 \(513\) 分。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=2e4+1;
ll n,m,u[N],v[N],w[N],dis1[N],dis2[N],path[N],pa[N],cnt,maxn=-1,taru,tarv,ans,maxx=-1;
vector<pair<ll,ll> > e[N],g[N];
bool vis[N];
void dijkstra(ll s)
{
memset(dis1,0x3f,sizeof(dis1));
memset(vis,0,sizeof(vis));
dis1[s]=0;
priority_queue<pair<ll,ll> > q;
q.push({0,s});
while(!q.empty())
{
ll u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto i:e[u])
{
ll v=i.first,w=i.second;
if(dis1[v]>dis1[u]+w)
{
dis1[v]=dis1[u]+w;
path[v]=u;
q.push({-dis1[v],v});
}
}
}
return;
}
void dijkstra2(ll s)
{
memset(dis2,0x3f,sizeof(dis2));
memset(vis,0,sizeof(vis));
dis2[s]=0;
priority_queue<pair<ll,ll> > q;
q.push({0,s});
while(!q.empty())
{
ll u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto i:g[u])
{
ll v=i.first,w=i.second;
if(dis2[v]>dis2[u]+w)
{
dis2[v]=dis2[u]+w;
path[v]=u;
q.push({-dis2[v],v});
}
}
}
return;
}
void getpath(ll x)
{
if(path[x]) getpath(path[x]);
pa[cnt++]=path[x];
return;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
e[u[i]].push_back({v[i],w[i]});
e[v[i]].push_back({u[i],w[i]});
}
dijkstra(1);
getpath(n);
ans=dis1[n];
pa[cnt]=n;
for(int x=1;x<cnt;x++)
{
taru=pa[x],tarv=pa[x+1];
for(int i=1;i<=m;i++) g[i].clear();
for(int i=1;i<=m;i++)
{
if(min(u[i],v[i])==min(taru,tarv) and max(u[i],v[i])==max(taru,tarv))
{
g[u[i]].push_back({v[i],2*w[i]});
g[v[i]].push_back({u[i],2*w[i]});
}
else
{
g[u[i]].push_back({v[i],w[i]});
g[v[i]].push_back({u[i],w[i]});
}
}
dijkstra2(1);
maxx=max(maxx,dis2[n]-ans);
}
cout<<maxx;
return 0;
}
E. 构造两个括号序列(450/1800)
\(\text{25%}\) 得分做法,考虑 dfs,每次我们维护一下拿出来的一个串和这个串中的所有元素的下标,然后得到另一个串。每当 dfs 到 now=s.length() 时,我们需要判断一下两串是否合法,如果合法就 ans++。当 now<s.length() 时,我们接下来有两种拿法,要么拿下一位,要么不拿,扔两个 dfs 递归,中间别忘了下标回溯。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=1001,mod=2012;
string s,tar1,tar2,ins;
ll ans;
set<ll> pos;
bool vis[N];
bool pd(string s)
{
if(s[0]==')') return 0;
if(s.length()%2==1) return 0;
stack<ll> st;
for(int i=0;i<s.length();i++)
{
if(s[i]=='(') st.push(i);
else if(s[i]==')')
{
if(!st.empty()) st.pop();
else return 0;
}
}
if(st.empty()) return 1;
return 0;
}
string makestr(set<ll> a)
{
string t;
for(int i=0;i<s.length();i++)
{
if(a.find(i+1)==a.end()) t+=s[i];
}
return t;
}
void dfs(ll now,string tar,set<ll> pos)
{
string tar2=makestr(pos);
if(now>s.length()) return;
if(now==s.length())
{
if(pd(tar)==1 and pd(tar2)==1) ans++,ans%=mod;
}
pos.insert(now+1);
dfs(now+1,tar+s[now],pos);
pos.erase(pos.find(now+1));
dfs(now+1,tar,pos);
return;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>s;
dfs(0,"",pos);
cout<<ans%mod;
return 0;
}
F. 完全匹配(600/2000)
\(\text{30%}\) 得分做法,我们考虑优化暴力,在枚举到长度是奇数的串和开头是右括号这样显然不合法的子串时直接跳过,卡常就可以过掉 \(\text{30pts}\)。
判断一个串是否合法就用括号匹配的方式即可,用一个栈来维护。
#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=5e3+1;
int k,n,ans;
string s[N],tar;
bool ok,ok2;
bool pd(string s)
{
stack<ll> st;
for(int i=0;i<s.length();i++)
{
if(s[i]=='(') st.push(i);
else if(s[i]==')')
{
if(!st.empty()) st.pop();
else return 0;
}
}
if(st.empty()) return 1;
return 0;
}
int main()
{
cin>>k>>n;
for(int i=1;i<=k;i++) cin>>s[i];
for(int i=0;i<n;i++)
{
ok2=0;
for(int cas=1;cas<=k;cas++)
{
if(s[k][i]==')')
{
ok2=1;
break;
}
}
if(ok2==1) continue;
for(int j=i+1;j<n;j++)
{
if((j-i+1)%2==1) continue;
ok=0;
for(int cas=1;cas<=k;cas++)
{
tar=s[cas].substr(i,j-i+1);
if(pd(tar)==0)
{
ok=1;
break;
}
}
if(ok==0) ans++;
}
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号