大连大学2022年4月4月程序设计竞赛题解
Solution A(数据泄露):
因为数据范围只有1000,且可能包含重边、自环和不连通,所以很容易想到flyod算法。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define imax 0x3f3f3f3f
#define lmax 0x3f3f3f3f3f3f3f3f
ll num[1005][1005];
int main(){
ll n,m;
cin>>n>>m;
memset(num,0x3f,sizeof num);
for(int i=1;i<=n;i++){
num[i][i]=0;
}
for(int i=0;i<m;i++){
ll a,b,s;
cin>>a>>b>>s;
num[a][b]=min(num[a][b],s);
num[b][a]=min(num[b][a],s);
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(num[i][k]<1e9&&num[k][j]<0x3f3f3f3f){
num[i][j]=min(num[i][j],num[i][k]+num[k][j]);
}
}
}
}
int k;
cin>>k;
int start=1;
ll re=0;
for(int i=0;i<k;i++){
int p;
cin>>p;
if(re==-1) continue;
if(num[start][p]>1e9){
re=-1;
continue;
}
re+=num[start][p];
start=p;
}
cout<<re<<endl;
return 0;
}
Solution B(波格丹危机):
签到题,按照题意排序即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define imax 0x3f3f3f3f
#define lmax 0x3f3f3f3f3f3f3f3f
int main(){
int n;
cin>>n;
vector<pair<int,string>> v;
while(n--){
string s;
int a;
cin>>s>>a;
v.push_back({a,s});
}
sort(v.begin(),v.end());
for(int i=0;i<v.size();i++){
if(v[i].second=="."||v[i].second=="!") cout<<v[i].second<<endl;
else cout<<v[i].second<<" ";
}
return 0;
}
Solution C(末日将至):
首先,如果发生全反射,说明一定能照射到全部数据包。
如果不能,当光照射在两个挡板交界处时,我们需要判断是否有数据包在光上面。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define imax 0x3f3f3f3f
#define lmax 0x3f3f3f3f3f3f3f3f
int main(){
int T;
cin>>T;
while(T--){
int a,b,n;
double k;
cin>>a>>b>>n>>k;
pair<int,int> nums[n];
for(int i=0;i<n;i++){
cin>>nums[i].first>>nums[i].second;
}
double tana=(double)b/(double)a;
double ana=atan(tana);
double sina=sin(ana);
double sinb=sina/k;
bool c=0;
if(fabs(sinb-1.0)>0.000001&&sinb>1.0){
c=1;
}
if(c==1){
cout<<"YES"<<endl;
continue;
}
double k2=tan(asin(sinb));
double b2=b-k2*a;
bool f=1;
for(int i=0;i<n;i++){
double y1=k2*nums[i].first+b2;
if(fabs(nums[i].second-y1)>0.000001&&nums[i].second>y1){
f=0;
}
}
if(f==1) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
Solution D(Raksasa的摸der):
本题考虑对多种情况的判断。
例如:d ,de 会对后面的r的贡献产生影响。算区间的时候不能把前面的d,de考虑进去。
解题思路是按照多种情况考虑,算清楚所有情况即可。运用前缀和计数。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
char c[N];
ll der[N][3],er[N][2],r[N];
int main()
{
ll n,x;
scanf("%lld%lld",&n,&x);
scanf("%s",c+1);
ll ans=0;
for(int i=1; i<=n; i++)
{
if(c[i]=='d')
{
der[i][0]=der[i-1][0]+1;
der[i][1]=der[i-1][1];
der[i][2]=der[i-1][2];
er[i][0]=er[i-1][0];
er[i][1]=er[i-1][1];
r[i]=r[i-1];
}
else if(c[i]=='e')
{
der[i][0]=der[i-1][0];
der[i][1]=der[i-1][1]+der[i-1][0];
der[i][2]=der[i-1][2];
er[i][0]=er[i-1][0]+1;
er[i][1]=er[i-1][1];
r[i]=r[i-1];
}
else if(c[i]=='r')
{
der[i][0]=der[i-1][0];
der[i][1]=der[i-1][1];
der[i][2]=der[i-1][2]+der[i-1][1];
er[i][0]=er[i-1][0];
er[i][1]=er[i-1][0]+er[i-1][1];
r[i]=r[i-1]+1;
}
else
{
der[i][0]=der[i-1][0];
der[i][1]=der[i-1][1];
der[i][2]=der[i-1][2];
er[i][0]=er[i-1][0];
er[i][1]=er[i-1][1];
r[i]=r[i-1];
}
}
for(int i=x;i<=n;i++)
{
ans=max(ans,der[i][2]-der[i-x][2]-der[i-x][0]*(er[i][1]-er[i-x][1]-er[i-x][0]*(r[i]-r[i-x]))-der[i-x][1]*(r[i]-r[i-x]));
}
printf("%lld\n",ans);
return 0;
}
也可以按区间一位一位算,这里给出验题人(Cantor.)的代码
#include<bits/stdc++.h>
#define oo 0x3f3f3f3f
#define OO 0x3f3f3f3f3f3f3f3f
#define LL long long
#define sz(x) int(x.size())
#define PII pair<int,int>
#define VI vector<int>
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define STP system("pause")
using namespace std;
const int N=1e6;
void Solve(){
int n,m;
string s;
cin>>n>>m>>s;
vector<LL> f(6);
//f0 = 'd' f1 = 'e' f2 = 'r' f3 = 'de' f4 = 'er' f5 = 'der'
auto Add=[&](char x){
if(x=='d') f[0]+=1;
if(x=='e') f[1]+=1,f[3]+=f[0];
if(x=='r') f[2]+=1,f[4]+=f[1],f[5]+=f[3];
};
auto Delete=[&](char x){
if(x=='d') f[0]-=1,f[3]-=f[1],f[5]-=f[4];
if(x=='e') f[1]-=1,f[4]-=f[2];
if(x=='r') f[2]-=1;
};
auto Print=[&](){
for(int i=0;i<6;++i)
cout<<f[i]<<" \n"[i==5];
};
for(int i=0;i<m;++i){
Add(s[i]);
}
LL ans=f[5];
for(int i=m;i<n;++i){
Delete(s[i-m]);
Add(s[i]);
ans=max(ans,f[5]);
}
cout<<ans<<'\n';
}
int main(){
Solve();
}
Solution E(数圆圈):
对于任意1e18以内的数,我们迭代几十次以后都会变成0-1-0-1的循环。
所以我们迭代出0或1以后直接算就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define imax 0x3f3f3f3f
#define lmax 0x3f3f3f3f3f3f3f3f
ll f(ll num){
string s=to_string(num);
ll re=0;
for(int i=0;i<s.size();i++){
if(s[i]=='6'||s[i]=='9'||s[i]=='0')re++;
if(s[i]=='8')re+=2;
}
return re;
}
int main() {
int T;
cin>>T;
while(T--){
ll n,k;
cin>>n>>k;
ll re=n;
while(k){
re=f(re);
k--;
if(re==0||re==1) break;
}
if(k%2==1){
if(re==0) re=1;
else re=0;
}
cout<<re<<endl;
}
return 0;
}
solution F(旅行):
考虑一个点在1-n某一条最短路径的条件,我们设dis(i,j)表示i到j的最短路径,如果点x在1-n的最短路径上,那么一定有dis(1,x)+dis(x,n)=dis(1,n),考虑到数据只有只有10^3级别,所以我们可以从1-n每个点出发跑一遍堆优化dijkstra,对于输入的x直接判断,时间复杂度O((n+m)^2log(n+m)+m)。
题解采用了另外一种做法。先正常建边以1号点为起点跑堆优化dijkstra,得到1到其他点的最短路径数组dis,然后建一张反图(如果存在边a->b,在反图中存在边b->a),以n号点为起点跑堆优化dijkstra,得到n到其他点的最短路径数组dis1,则对于输入的x,我们直接判断dis(1,x)+dis1(n,x)是否等于dis(1,n)即可,时间复杂度O((n+m)log(n+m)+m)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF 2147483647
#define N 100005
#define M 200005
using namespace std ;
struct Edge { int nxt,to,dist ; } e[M],e1[M] ;
struct node {
int id,d ;
bool operator <(const node &a) const {
return d>a.d ;
}
};
int head[N],head1[N],tot1=0,tot=0 ;
int n,m,S ;
int dis[N],dis1[N] ;
bool vis[N] ;
priority_queue<node> q ;
inline void add(int from,int to,int dist) {
e[++tot].to=to ; e[tot].dist=dist ;
e[tot].nxt=head[from] ; head[from]=tot ;
}
inline void add1(int from,int to,int dist) {
e1[++tot1].to=to ; e1[tot1].dist=dist ;
e1[tot1].nxt=head1[from] ; head1[from]=tot1 ;
}
void dijkstra(int S) {
fill(vis+1,vis+n+1,0) ;
fill(dis+1,dis+n+1,INF) ;
dis[S]=0 ; q.push((node){S,0}) ;
while(!q.empty()) {
node x=q.top() ; q.pop() ;
if(vis[x.id]) continue ;
vis[x.id]=1 ;
for(int i=head[x.id];i;i=e[i].nxt) {
int y=e[i].to ;
if(dis[y]>dis[x.id]+e[i].dist) {
dis[y]=dis[x.id]+e[i].dist ;
if(!vis[y]) q.push((node){y,dis[y]}) ;
}
}
}
}
void dijkstra1(int S) {
fill(vis+1,vis+n+1,0) ;
fill(dis1+1,dis1+n+1,INF) ;
dis1[S]=0 ; q.push((node){S,0}) ;
while(!q.empty()) {
node x=q.top() ; q.pop() ;
if(vis[x.id]) continue ;
vis[x.id]=1 ;
for(int i=head1[x.id];i;i=e1[i].nxt) {
int y=e1[i].to ;
if(dis1[y]>dis1[x.id]+e1[i].dist) {
dis1[y]=dis1[x.id]+e1[i].dist ;
if(!vis[y]) q.push((node){y,dis1[y]}) ;
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&S) ;
for(int i=1,u,v,w;i<=m;++i) {
scanf("%d%d%d",&u,&v,&w) ;
add(u,v,w) ; add1(v,u,w) ;
}
dijkstra(1) ;
dijkstra1(n) ;
while(S--)
{
int x ;
scanf("%d",&x) ;
if(1LL*dis[x]+dis1[x]==1LL*dis[n])
printf("yes\n") ;
else printf("no\n") ;
}
return 0 ;
}
Solution G(全体目光看向我,我宣布个事):
单纯的统计,实在不行也可以开26个变量来计数。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
ll buc[N];
int main()
{
ll n;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
char ch;
scanf(" %c",&ch);
buc[ch]++;
}
ll ans=0;
char ch;
for(int i='A';i<='Z';i++)
{
if(ans<buc[i])
{
ans=buc[i];
ch=i;
}
}
printf("%c",ch);
return 0;
}
Solution H(寻找最大差值):
我们创建两个数组,第一个数组是从下标0到i的最小值,第二个数组是从最后面到i的最大值。
然后遍历这两个数组,使用最大值减去最小值即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define imax 0x3f3f3f3f
#define lmax 0x3f3f3f3f3f3f3f3f
int main(){
int T;
cin>>T;
while(T--){
int n;
cin>>n;
int num[n];
for(int i=0;i<n;i++){
cin>>num[i];
}
deque<int> mx;
deque<int> mi;
for(int i=0;i<n;i++){
if(i==0) mi.push_back(num[0]);
else{
mi.push_back(min(mi.back(),num[i]));
}
}
for(int i=n-1;i>=0;i--){
if(i==n-1) mx.push_front(num[n-1]);
else mx.push_front(max(num[i],mx.front()));
}
int re=-1;
for(int i=0;i<n;i++){
if(mx[i]>mi[i])re=max(re,mx[i]-mi[i]);
}
cout<<re<<endl;
}
return 0;
}
另一份代码:
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
ll a[N];
int main()
{
ll t;
scanf("%lld",&t);
while(t--)
{
ll n;
scanf("%lld",&n);
ll num=0;
ll ans=-1;
for(int i=0;i<n;i++) scanf("%lld",&a[i]);
for(int i=n-1; i>=0; i--)
{
num=max(a[i],num);
if(num-a[i]>0) ans=max(num-a[i],ans);
}
printf("%lld\n",ans);
}
return 0;
}
solution I(彩虹) :
7种颜色染格子,要求相邻格子颜色不相同,求染色的方案数。
经典状压dp。
我们令dp(i,j)表示第i行状态为j的染色方案数,其中状态j模拟一个n位7进制数,第k位为0-6分别代表格子(i,k)上的颜色为第1-7种颜色,则有dp转移方程
答案即为
但是本题要注意处理一下一行的合法颜色,不然会TLE
#include<bits/stdc++.h>
#define N 65536
#define mod 1000000007
#define LL long long
using namespace std ;
int q[N],n,m,num=0 ;
LL f[15][N] ;
bool check(int len,int x,int y)
{
int s,t ;
while(len--)
{
s=x%7,t=y%7 ;
if(s==t) return false ;
x/=7 ; y/=7 ;
}
return true ;
}
int main()
{
cin>>n>>m ;
int res=1 ;
for(int i=1;i<=n;++i) res*=7 ;
for(int i=0;i<res;++i)
{
int len=n,s=i/7,lst=i%7 ;
bool flag=1 ;
while(--len)
{
if(s%7==lst)
{
flag=0 ;
break ;
}
lst=s%7 ; s/=7 ;
}
if(flag) q[++num]=i ;
}
memset(f,0,sizeof(f)) ;
for(int i=1;i<=num;++i) f[1][i]=1 ;
for(int i=2;i<=m;++i)
{
for(int j=1;j<=num;j++)
for(int k=1;k<=num;++k)
if(check(n,q[j],q[k]))
f[i][j]=(f[i][j]+f[i-1][k])%mod ;
}
LL ans=0 ;
for(int i=1;i<=num;++i)
ans=(ans+f[m][i])%mod ;
cout<<ans<<endl ;
return 0 ;
}
Solution J(突然的自我):
签到题,根据题意,很容易推出f(i)=f(i-1)+f(i-2)+1。
Solution K(Raksasa的轻功):
因为跳法的原因,可以判断出具有传递性。
提供两种思路:
一种是判断高低,记录每一座山峰往左和往右能跳到的最低山峰位置
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
ll a[N];
ll pre[N],suf[N];
int main()
{
ll n;
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
ll mark=1;
for(int i=1;i<=n;i++)
{
if(a[i]>a[i-1]) pre[i]=mark;
else pre[i]=mark=i;
}
mark=n;
for(int i=n;i>=1;i--)
{
if(a[i]>a[i+1]) suf[i]=mark;
else suf[i]=mark=i;
}
ll ans=0;
for(int i=1;i<=n;i++) ans=max({ans,i-pre[i],suf[i]-i});
printf("%lld\n",ans);
return 0;
}
另一种是直接从左跑到右,从右跑到左,各跑一遍。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
ll a[N];
int main()
{
ll n;
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
ll up=0,down=0;
ll ans=0;
for(int i=1;i<n;i++)
{
if(a[i]<a[i+1])
{
up++;
down=0;
}
else if(a[i]>a[i+1])
{
down++;
up=0;
}
else
{
up=0;
down=0;
}
ans=max({ans,up,down});
}
printf("%lld\n",ans);
return 0;
}
solution L(函数求和):
一句话题意:
显然
所以
直接求和取模即可,时间复杂度O(m)
#include<bits/stdc++.h>
#define N 10000005
#define mod 1000000007
#define LL long long
using namespace std ;
int a[N],c[N],n,m ;
inline void read(int &num)
{
int s = 0 ; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') s = (s<<3) + (s<<1) + ch - '0', ch = getchar();
num = s;
}//快读,可以用scanf代替
int main() {
int n,m ;
read(n),read(m) ;
for(int i=1;i<=n;++i) read(a[i]) ;
LL ans=0 ; int x ;
while(m--)
{
read(x) ; LL res ;
if(x==a[x]) res=x ;
else res=1 ;
ans=(ans+res)%mod ;
}
printf("%lld\n",ans) ;
return 0 ;
}
Solution M(Raksasa的棋局):
本题可以预处理出馬踩的所有的位置最快需要多少步(bfs),将其存储在数组中,O(1)查询即可
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1010
#define mod 1000000007
#define mod2 998244353
#define ll long long
ll dp[N][N];
ll dx[8]={1,2,2,1,-1,-2,-2,-1};
ll dy[8]={-2,-1,1,2,-2,-1,1,2};//馬的8个走向
int main()
{
ll n,m,q;
scanf("%lld%lld%lld",&n,&m,&q);
ll x1,y1;
scanf("%lld%lld",&x1,&y1);
queue<pair<ll,ll>> qu;
qu.push({x1,y1});
while(!qu.empty())
{
pair<ll,ll> pa=qu.front();
qu.pop();
for(int i=0;i<8;i++)
{
ll x=pa.first+dx[i];
ll y=pa.second+dy[i];
if(x==x1&&y==y1) continue;
if(x<1||x>n||y<1||y>m) continue;
if(!dp[x][y])
{
dp[x][y]=dp[pa.first][pa.second]+1;
qu.push({x,y});
}
}
}
while(q--)
{
ll x,y;
scanf("%lld%lld",&x,&y);
if(dp[x][y]) printf("%lld\n",dp[x][y]);
else printf("-1\n");
}
return 0;
}
Solution N(Raksasa的数字):
把数组中每一个数字都转换成二进制,异或1会把数字“翻转”
例如: 1 异或 1为0,0异或 1 为 1。
统计每一位(转换成二进制)上有多少个1和0,因为异或1会把所有的1变成0,0变成1。所有就等同于1和0的数目交换。所有就算一下性价比,尽量往小的换即可。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x7ffffffffffffff
#define N 1005000
#define mod 1000000007
#define mod2 998244353
#define ll long long
vector<bitset<32>> ve;
ll cnt[32];
int main()
{
ll t;
scanf("%lld",&t);
while(t--)
{
ll n;
ve.clear();
scanf("%lld",&n);
for(int i=0;i<n;i++)
{
ll x;
scanf("%lld",&x);
bitset<32> bi(x);
ve.push_back(bi);
}
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++)
{
for(int j=0;j<32;j++)
{
if(ve[i][j]) cnt[j]++;
}
}
ll ans=0;
for(int i=0;i<32;i++)
{
if(cnt[i]>n/2)
{
ans+=(1ll<<i);
}
}
printf("%lld\n",ans);
}
return 0;
}