AHUACM 第二场摸底测试复盘题解

AHUACM第二场摸底测试复盘题解

A:Bottles(2016-2017 ACM-ICPC, NEERC, Southern Subregional Contest J)

难度:1900

这题只要注意到n=100对应的是\(O(n^4)\)的时空复杂度,就是比较水的dp了。(不过被2分钟就切的yb误导了,以为是脑筋急转弯)

性质:容器瓶子需要装的水量恒定

f[i][j][k]表示在前i个瓶子中,已经选了j个瓶子作为容器,容器总容量为k时,需要往容器倒的水的最小值

#include<bits/stdc++.h>
using namespace std;
int a[105],b[105];
int f[101][101][10001];
int main()
{
  int n;
  cin>>n;
  int sum=0;
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];
    sum+=a[i];
  }
  for(int i=1;i<=n;i++)
  {
    cin>>b[i];
  }
  for(int i=0;i<=n;i++)
  {
    for(int j=0;j<=n;j++)
    {
      for(int k=0;k<=10000;k++)
      {
        f[i][j][k]=30000;  
      }
    }
  }
  f[0][0][0]=0;
  for(int i=1;i<=n;i++)
  {
    for(int j=0;j<=n;j++)
    {
      for(int k=0;k<=10000;k++)
      {
        f[i][j][k]=min(f[i-1][j][k]+a[i],f[i][j][k]);
        if(j>=1&&k>=b[i])f[i][j][k]=min(f[i][j][k],f[i-1][j-1][k-b[i]]);
      }
    }
  }
  int ans1=0,ans2=30000;
  for(int i=1;i<=n;i++)
  {
    for(int j=sum;j<=10000;j++)
    {
      if(f[n][i][j]<=j)
      {
        ans1=i;
        break;
      }
    }
    if(ans1)break;
  }
  for(int j=sum;j<=10000;j++)
  {
    if(f[n][ans1][j]<=j)
    {
      ans2=min(f[n][ans1][j],ans2);
    }
  }
  cout<<ans1<<" "<<ans2<<endl;
  return 0;
}

B:Fish(Codeforces Beta Round 16 E)

难度:1900

一看数据范围就知道是裸地不能再裸的状压dp,状态就是当前还剩的鱼的集合

#include<bits/stdc++.h>
using namespace std;
#define LL long long 
double f[20][(1<<18)+10];
double p[20][20];
int t[20];
int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
  {
    for(int j=1;j<=n;j++)
    {
      cin>>p[i][j];
    }
  }
  f[0][(1<<n)-1]=1;
  for(int i=0;i<n-1;i++)
  {
    for(int j=0;j<(1<<n);j++)
    {
      if(f[i][j]<1e-9) continue;
      int m=0;
      for(int k=0;k<n;k++)
      {
        if((j&(1<<k))==0) continue; 
        t[++m]=k+1;
      }
      if(i!=n-m)continue;
      double cnt=1.0*m*(m-1)/2;
      for(int x=1;x<=m;x++)
      {
        for(int y=x+1;y<=m;y++)
        {
          f[i+1][j^(1<<(t[y]-1))] += f[i][j] * p[t[x]][t[y]] / cnt;
          f[i+1][j^(1<<(t[x]-1))] += f[i][j] * p[t[y]][t[x]] / cnt;
        }
      }
    }
  }
  for(int i=1;i<=n;i++)
  {
    printf("%.10f ",f[n-1][1<<(i-1)]);
  }
  return 0;
}

C: Coloring Game(Pinely Round 4 (Div. 1 + Div. 2) E)

难度:1900

这题本质上就是二分图判断。

当给出的图不是二分图时,Alice只需要只给两种颜色,就能自动胜利

当给出的图是二分图时,Bob 必胜法如下:

当二分图两侧点都没用完时,可以忽略颜色3,只用颜色1或颜色2。而当其中一侧的点用完后,就用另一侧点原本对应的颜色或颜色3填另一侧的点。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 10005;
vector<int>e[maxn];
int fa[maxn],col[maxn];
bool ck;
void dfs(int u)
{
  for(int v:e[u])
  {
    if(fa[u]==v)continue;
    if(col[v]!=-1)
    {
      if(col[v]==col[u])
      {
        ck=true;
        return;
      }
    }else
    {
      col[v]=col[u]^1;
      fa[v]=u;
      dfs(v);
    }
  }
}
void solve()
{
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++)
  {
    e[i].clear();
    col[i]=-1;
  } 
  for(int i=1;i<=m;i++)
  {
    int u,v;
    cin>>u>>v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  ck=0;col[1]=0;
  dfs(1);
  if(ck)
  {
    cout<<"Alice"<<endl;
    for(int i=1;i<=n;i++)
    {
      cout<<1<<" "<<2<<endl;
      int u,c;
      cin>>u>>c;
    }
  }else
  {
    cout<<"Bob"<<endl;
    set<int>c1,c2;
    for(int i=1;i<=n;i++)
    {
      if(col[i]==0)c1.insert(i);
      else c2.insert(i);
    }
    int c3=0;
    for(int i=1;i<=n;i++)
    {
      int a,b;
      cin>>a>>b;
      if(a>b)swap(a,b);
      if(c1.size()&&c2.size())
      {
        if(a==1)
        {
          cout<<(*c1.begin())<<' '<<1<<endl;
          c1.erase(c1.begin());
        }
        else
        {
          cout<<(*c2.begin())<<' '<<2<<endl;
          c2.erase(c2.begin());
        }
      }else if(c1.size())
      {
        if(a==1)
        {
          cout<<(*c1.begin())<<' '<<1<<endl;
          c1.erase(c1.begin());
        }
        else
        {
          cout<<(*c1.begin())<<' '<<3<<endl;
          c1.erase(c1.begin());
        }
      }else if(c2.size())
      {
        if(a==2||b==2)
        {
          cout<<(*c2.begin())<<' '<<2<<endl;
          c2.erase(c2.begin());
        }
        else
        {
          cout<<(*c2.begin())<<' '<<3<<endl;
          c2.erase(c2.begin());
        }
      }
    }
  }
}
int main()
{
  int T;
  cin>>T;
  while(T--)
  {
    solve();
  }
  return 0;
}

D: Ananna(The 2025 ICPC Latin America Championship A)

bfs,恰好只需把所有点对(i,j)跑一次。

关键性质:一个回文点对(i,j)可以由另外一个回文点对扩展出来。

一开始的思路类似于弗洛伊德,始终不知道怎么O(1)在不同的状态之间进行转移。

只能说在涉及图论的题目时,搜索确实是比递推更具有优越性的。

以后遇到图论题时,首先应该考虑能否由bfs解决

时间复杂度证明:

一个点对(i,j)最多只会进队一次,这部分复杂度不超过\(O(n^2)\)

然后是枚举边的部分:

\[\begin{aligned} &\sum_{i}^{n}\sum_{j}^{n}\sum_{V_i}\sum_{V_j}\\ \iff&\sum_{i}^{n}\sum_{j}^{n}V_iV_j\\ \iff&\sum_{i}^{n}V_i\sum_{j}^{n}V_j\\ \iff&m^2 \end{aligned} \]

因此总复杂度为\(O(n^2+m^2)\)

#include<bits/stdc++.h>
using namespace std;
#define LL unsigned long long
const int maxn = 5005;
vector<pair<int,char>>e1[maxn],e2[maxn];
bool f[maxn][maxn];
int main()
{
  int n,m;
  cin>>n>>m;
  queue<pair<int,int>>q;
  for(int i=1;i<=m;i++)
  { 
    int x,y;
    char c;
    cin>>x>>y>>c;
    e1[x].push_back({y,c});
    e2[y].push_back({x,c});
    if(!f[x][y])
    {
      f[x][y]=1;
      q.push({x,y});  
    }
  }
  for(int i=1;i<=n;i++)
  {
    q.push({i,i});
  }
  while(!q.empty())
  {
    int x=q.front().first;
    int y=q.front().second;
    q.pop();
    for(auto dx:e2[x])
    {
      for(auto dy:e1[y])
      {
        if(dx.second==dy.second && !f[dx.first][dy.first])
        {
          f[dx.first][dy.first]=1;
          q.push({dx.first,dy.first});
        }
      }
    }
  }
  int ans=0;
  for(int i=1;i<=n;i++)
  {
    for(int j=1;j<=n;j++)
    {
      if(f[i][j]&&i!=j)
      {
        ans++;
      }
    }
  }
  coutans;
  return 0;
}

E: Baby Ehab Partitions Again(Codeforces Round 717 C)

难度:1700

可以注意到,想把数列分成和相等的两部分,数列和一定是偶数,很容易想到:删除一个奇数,就能把它变成奇数。
并且,数列中的每个数除以数列gcd队划分不会产生影响,当数列中没有奇数时,这么处理能保证获得至少一个奇数,将其删除即可。

f[i][j]表示能否从前i个数中凑出部分数列和j,f[n][sum/2]就能判断能否分成两部分。

#include<bits/stdc++.h>
using namespace std;
bool f[105][200005];
int a[105],n,sum,cnt;
bool check()
{
  if(sum%2==1)return false;
  f[0][0]=1;
  for(int i=1;i<=n;i++)
  {
    for(int j=1;j<=sum;j++)
    {
        .
      f[i][j]=f[i-1][j];
      if(j>=a[i])
      {
        f[i][j]|=f[i-1][j-a[i]];
      }
    }
  }
  return f[n][sum/2];
}
bool check2()
{
  for(int i=1;i<=n;i++)
  {
    if(a[i]%2!=0)return 0;
  }
  return 1;
}
int main()
{
  cin>>n;
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];
    sum+=a[i];
  }
  if(check()==0)
  {
    cout<<0<<endl;
  }else
  {
    while(check2())
    {
      cnt++;
      for(int i=1;i<=n;i++)
      {
        a[i]/=2;
      }
    }
    
    for(int i=1;i<=n;i++)
    {
      if(a[i]%2)
      {
        cout<<1<<endl<<i<<endl;
        return 0;
      }
    }
  }
  return 0;
}

F Isomorphic Strings(Educational Codeforces Round 44 F)

难度:2300

赛时想到哈希,但是不知道怎么哈

一个长度为n的字符串,它所有字符的情况可以由26个长度为n的01串表示

而这个长度为n的01串可以被哈希化为一个整数,因此只需要26个整数就可以表示一个字符串的所有字母的分布情况

判断两个字符串是否是同构的,只需要判断它们的26个整数是否相等即可

因此对于这道题,我们用f[i][c]表示前i个字符中,字符c的分布情况对应的哈希值,就能在\(O(26)\)知道任何一个区间的字符分布情况:

也就是把26个哈希值塞进一个vector并排序,得到的vector就代表了这个区间的字符分布情况

#include<bits/stdc++.h>
using namespace std;
#define LL unsigned long long
const LL mod = 1e9+7;
const LL base=131;
const int maxn = 200005;
LL f[maxn][26],bs[maxn];
LL query(int l,int r,int c)
{ 
  if(l==0)return f[r][c];
  return (f[r][c]-f[l-1][c]*bs[r-l+1]%mod+mod)%mod;
}
void solve()
{
  int n,m;
  cin>>n>>m;
  string s;
  cin>>s;
  for(int i=0;i<s.length();i++)
  {
    for(int j=0;j<26;j++)
    {
      if(i==0)f[i][j]=s[i]==(j+'a');
      else f[i][j]=(f[i-1][j]*base%mod+(s[i]==(j+'a'))) % mod;
    }
  }
  bs[0]=1;
  for(int i=1;i<n;i++)
  {
    bs[i]=bs[i-1]*base%mod;
  }
  while(m--)
  {
    int x,y,len;
    vector<int>a,b;
    cin>>x>>y>> len;
    x--; y--;
    for(int i=0;i<26;i++)
    {
      a.push_back(query(x,x+len-1,i));
      b.push_back(query(y,y+len-1,i));
    }
    sort(a.begin(),a.end());
    sort(b.begin(),b.end());
    if(a == b)
    {
      cout<<"YES"<<endl;
    }
    else
    {
      cout<<"NO"<<endl;
    }
  }

}
int main()
{
  int T=1;
  while(T --> 0)
  {
    solve();
  }
  return 0;
}
posted @ 2025-07-12 23:51  arthalter  阅读(11)  评论(0)    收藏  举报