AtCoder Beginner Contest 292(E,F,G)

发布时间 2023-05-28 19:12:17作者: righting

AtCoder Beginner Contest 292(E,F,G)

E(图)

E

这个题的大意是给你一个有\(n\)个点,\(m\)条单向边的图,然后我们需要把满足下面条件的点连接在一起,问最少需要连接多少条边

对于\(a\)有一条边可以到\(b\),然后\(b\)有一条边可到\(c\),但是\(a\)没有边可以到达\(c\)的时候,我们需要添加一条从\(a\)\(c\)的边

按照以上的条件,(自己画出几条边试试)我们就会发现,如果存在一条路径,里面的点的数量大于\(2\),那么其中的点一定需要连接和这个点距离大于等于\(2\)的点,而,距离为\(1\)的点一定是本来就连接的,那么我们所需要的就是那些距离大于\(2\)且还未连接的点,但是我们不太好找哪些边已经连接好了(一个一个找太麻烦了),我们不如先求出这个图最后的边数(一定是每一个点所可以到达的点数之和),然后我们再减去原来最初的边数\(m\)

这个我感觉还蛮好写的

但是,我发现了一个很新颖的写法

它是记录一个点的前面一个,一个点的后面一个

根据条件,我们可以知道,要添加边的是一个点的前面一个点和这个点和后面一个点

所以,它遍历的是中间的那一个点,然后根据他前后两个点是否连接来判断是否需要添加边

但是,我们会不会觉得如果我们以这个点作为中间点,但是后面出现一条边,可以让这个点再次成为一个中间点,但是这个点已经遍历完了,会不会导致答案错误

我目前没有发现这种情况(我自己画了一段点,然后用正向和反向都试过了,没有问题)

我觉得在遍历完点时候,把这一个点删除,原来距离为\(2\)的两个边,已经连在一起,距离变成了\(1\),相当于把原来这个点相连的点的信息添加到了它它相邻的点里面去了(,可以和上面的只要是和它相连通的点,都需要连接的感觉

我的代码是后面的写法

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <stack>
#include<cmath>
#include <unordered_map>
#include <array>
#include <cstring>
using namespace std;
#define int long long 
#define LL long long
#define ios  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define inf 1e18
#define INF 1e18
#define mem(a,b) memset((a),(b),sizeof(a))
const int maxn=2e3+10;;
const int mod=998244353;
int n,m;
vector<vector<int>>in,out;
vector<vector<bool>>g;
signed main ()
{
    cin>>n>>m;
    in.resize(n+1);
    out.resize(n+1);
    g=vector<vector<bool>>(n+1,vector<bool>(n+1,false));
    int ans=0;
    for (int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        in[v].push_back(u);
        out[u].push_back(v);
        g[u][v]=true;
    }
    for (int i=1;i<=n;i++)
    {
        for (auto x:in[i])
        {
            for (auto y:out[i])
            {
                if(x==y) continue;
                if(!g[x][y])
                {
                    ans++;
                    g[x][y]=true;
                    in[y].push_back(x);
                    out[x].push_back(y);
                }
            }
        }
    }
    cout<<ans<<"\n";
    system ("pause");
    return 0;
}

F(计算几何)

F

这个题目大意就是给你一个矩形的长宽,问我们可以把一个最大边长为多少的正三角形完全放进这个矩形里面去

我觉得这个题解讲得很清楚,博客

它假设这个正三角形的一个点在这个矩形的一个顶点上,一个点在矩形的边上,我们已经知道两个点了,还已知这个角的角度,(正三角形)而且根据向量的旋转,我们可以得到第三个点的坐标范围

对于旋转,我之前了解很少,没想到有公式

旋转公式

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <stack>
#include<cmath>
#include <unordered_map>
#include <array>
#include <cstring>
using namespace std;
#define int long long 
#define LL long long
#define ios  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define inf 1e18
#define INF 1e18
#define mem(a,b) memset((a),(b),sizeof(a))
const int maxn=2e3+10;;
const int mod=998244353;
double A,B;
signed main ()
{
    cin>>A>>B;
	if(A>B)
		swap(A,B);
    double ans1=2*B-sqrt(3)*A;
    double ans2=A/sqrt(3);
    double ans=min(ans1,ans2);
    ans=sqrt(A*A+ans*ans);
    printf("%.15lf\n",ans);
    system ("pause");
    return 0;
}

G(dp)

G

题目大意就是给你\(n\)个长度为\(m\)的字符串,其中有数字,有问号,我们要把这些问号变成\(0\)\(9\)里面的数字,最后这些字符串的可以看成是一个数字,这\(n\)字符串最后是单调递增的

学习

下面是我自己的一些理解

遍历数位,从后往前,然后对于这一位,然后再一个一个求出\(1,n\)这些字符串的这个位置上是否符合条件(递增)

然后我们再来看对于某一个位置,这\(n\)个字符串该如何分配数字

我们首先得到一个区间\(l,r\),可以看做是一个初状态

如果这一段区间出现过的数字只有一个,那么在这个区间里面出现过的问号也可以变成这个数字,

那么我么假设\(h [L] [R] [l] [r]\)\(l,r\)这一段区间里面的数字范围一定是\(L\)\(R\)的,而且是递增的,我们还需要记录每一次在\(l,r\)这一区间满足条件的有多少种\(g[l] [r]\)

假设这个数字是\(w\)

那么此时我们可以得到\(h[w] [w] [l] [r]=1\times g[l] [r]\),其中\(g\)是上一轮的\(f\)

如果这一段区间没有出现过一个数字,那么在这个区间里面出现过的问号也可以变成任何数字

所以\(h[w] [w] [l] [r]=1\times g[l] [r]\),这里面的\(w\)每一种都要遍历出来

然后我们在根据确定一个位置而合并两个区间的情况

然后我们遍历这个位置\(mid\)变成\(R\),可以合并\(l,mid\)和区间\(mid+1,r\),但是注意这些区间里面的数的范围

可以得到状态转移方程如下

\[h[L][R][l][r]+=h[L][R-1][l][mid]\times h[R][R][mid+1][r] \]

\(mid\)前面的一定不能大于\(R\),也不能等于题目是大于,而不是大于等于)

然后还有一步,就是处理好\(h,f\)的状态

\(f\)是这一位不同区间满足条件的数量,我们可以先通过\(h\)先把\(l,r\)区间可能的不同范围求出来,然后更新加到\(f\)里面,不管怎样,都要不遗漏,不重复的把可能的数量求出来

我感觉每一位大致可以分为三步

第一步,先遍历长度,得到不同\(l,r\)

第二步,

\(1\),这个区间\(l,r\)不考虑其他区间,而考虑"初状态",判断这个区间里面出现的数字个数

\(2\),根据两个不同区间合并而来的

第三步,更新\(f\)

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <bitset>
#include <queue>
#include <stack>
#include<cmath>
#include <unordered_map>
#include <array>
#include <cstring>
using namespace std;
#define int long long 
#define LL long long
#define ios  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define inf 1e18
#define INF 1e18
#define mem(a,b) memset((a),(b),sizeof(a))
const int maxn=45;
const int mod=998244353;
int n,m,w;
string s[maxn];
int h[11][11][maxn][maxn];
void add(int &x,int c)
{
    x+=c;
    if(x>=mod)
    {
        x-=mod;
    }
    return ;
}
void init()
{
    for (int i=0;i<10;i++)
    {
        for (int j=0;j<10;j++)
        {
            for (int l=1;l<=n;l++)
            {
                for (int r=1;r<=n;r++)
                {
                    h[i][j][l][r]=0;
                }
            }
        }
    }
    return ;
}
signed main ()
{
    cin>>n>>m;
    vector<vector<int>>f(n+1,vector<int>(n+1,0));
    vector<vector<int>>g(n+1,vector<int>(n+1,0));
    for (int i=1;i<=n;i++)
    {
        cin>>s[i];
        s[i]=" "+s[i];
    }
    for (int i=1;i<=n;i++)
    {
        f[i][i]=1;
    }
    for (int pos=m;pos>=1;pos--)
    {
        g=f;
        f=vector<vector<int>>(n+1,vector<int>(n+1,0));
        init();
        for (int i=1;i<=n;i++)
        {
            for (int l=1,r=i;r<=n;l++,r++)
            {
               w=0;
               for (int k=l;k<=r;k++)
               {
                if(s[k][pos]!='?')
                {
                    w|=1ll<<(s[k][pos]-'0');
                }
               }
               if(w==0)
               {
                for (int k=0;k<10;k++)
                {
                    h[k][k][l][r]=g[l][r];//相当于把这个位置赋为k,方式有 1*g[l][r]
                }
               }
               else if(w==(w&-w))//只有一个1的情况,那就是只出现了一种数,二进制表达只有一个1
               {
                w=__builtin_ctz(w);//这个1在哪一个位置
                h[w][w][l][r]=g[l][r];
               }
               for (int mid=l;mid<r;mid++)//考虑合并
               {
                for (int L=0;L<10;L++)
                {
                    for (int R=L+1;R<10;R++)
                    {
                        h[L][R][l][r]=(h[L][R-1][l][mid]*h[R][R][mid+1][r]+h[L][R][l][r])%mod;
                    }
                }
               }
               for (int L=0;L<10;L++)//更新f
               {
                for (int R=L+1;R<10;R++)
                {
                    add(h[L][R][l][r],h[L][R-1][l][r]);
                }
                add(f[l][r],h[L][9][l][r]);
               }
            }
        }
    }
    int ans=f[1][n];
    cout<<ans<<"\n";
    system ("pause");
    return 0;
}