AtCoder Beginner Contest 209 题解(A-E)

AtCoder Beginner Contest 209 题解(A-E)

A. Counting

题目大意:

问有多少整数大于等于 A A A且小于等于 B B B

解题思路:

签到题,注意一下 A A A是有可能大于 B B B

代码:

#include
#include
using namespace std;
int main()
{
    int a,b;
    scanf("%d%d",&a,&b);
    printf("%d\n",max(0,b-a+1));
    return 0;
}

B. Can you buy them all?

题目大意:

给出 n n n个物品和单价,其中第偶数个的物品会便宜一元,问x元能否买下所有的物品。

解题思路:

签到题,模拟即可。

代码:

#include
#include
using namespace std;
int n,x;
int main()
{
    cin>>n>>x;
    int sum=0;
    for(int i=0;i>t;
        sum+=t;
    }
    sum-=n/2;
    puts(sum<=x?"Yes":"No");
    return 0;
}

C. Not Equal

题目大意:

给你一个由N个整数组成的序列 C C C,问能找到多少个由 N N N个整数组成的序列A满足以下条件

  • 1 ≤ A i ≤ C i ( 1 ≤ i ≤ N ) 1\le A_i\le C_i(1\le i \le N) 1AiCi(1iN)
  • A i ≠ A j ( 1 ≤ i < j ≤ N ) A_i\ne A_j(1\le i < j \le N) Ai=Aj(1i<jN)

答案要对 1 0 9 + 7 10^9+7 109+7取模。

解题思路:

其实这道题就是通过样例找灵感。

例如 C = ( 3 , 3 , 4 , 4 ) C=(3,3,4,4) C=(3,3,4,4)

首先考虑 A 1 A_1 A1,不难发现 A 1 A_1 A1能从 ( 1 , 2 , 3 ) (1,2,3) (1,2,3)中任选一个,所以 A 1 A_1 A1有3种选择。

然后再考虑 A 2 A_2 A2 A 2 A_2 A2同样能从 ( 1 , 2 , 3 ) (1,2,3) (1,2,3)中任选一个,但因为第二个条件的限制, A 2 A_2 A2不能与 A 1 A_1 A1相同,所以 A 2 A_2 A2有2种选择。

继续考虑 A 3 A_3 A3, A 3 A_3 A3本来能从 ( 1 , 2 , 3 , 4 ) (1,2,3,4) (1,2,3,4)中任选一个的,但同样因为第二个条件的限制, A 3 A_3 A3不能与 A 1 A_1 A1 A 2 A_2 A2相同,所以 A 3 A_3 A3只有2种选择。

同理, A 4 A_4 A4只有1种选择,所以答案就是 3 × 2 × 2 × 1 = 12 3\times 2 \times 2 \times1=12 3×2×2×1=12

所以我们可以从此获得启发,将 C C C中所有的数按照升序排序,从小到大考虑每个数的选择情况。

对于排序后的 C i C_i Ci,因为之前已经有 i − 1 i-1 i1个数已经选择过了,同时我们也是按照升序排列的,那么这些选择的数也必然在 [ 1 , C i ] [1,C_i] [1,Ci]范围内,所以对于 C i C_i Ci的可选项就缩减为 C i − ( i − 1 ) C_i-(i-1) Ci(i1)。特殊地,当 C i < = i − 1 C_i<=i-1 Ci<=i1的时候,找不到符合条件的 A A A,答案为0。

代码:

#include
#include
using namespace std;
typedef long long LL;
const int N=2e5+10,MOD=1e9+7;
int n;
int a[N];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    LL res=1;
    for(int i=1;i<=n;i++)       res=(res*(a[i]-i+1))%MOD;
    printf("%d\n",res);
    return 0;
}

D. Collision

题目大意:

给出一棵树,一共有 Q Q Q次询问,每次询问分别位于 c i c_i ci d i d_i di节点的两个人同时沿着树上的边双向奔赴的话,最终相遇的位置是在节点处还是在树边上。

解题思路:

其实最为清晰的做法是将整棵树用染色法染色,位于不同颜色的两个节点肯定在节点相遇,否则就在路上相遇。

但我比赛的时候是预处理出所有点到某个点的距离,每次判断一下两个点的距离之和的奇偶性,如果是偶数就在节点相遇,否则就在路上相遇。其实都是本质上都是一样的。

代码:

#include
#include
const int N=1e5+10,M=2*N;
int h[N],e[M],ne[M],idx;
int n,m;
int d[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int pre,int step){
    d[u]=step;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j!=pre) dfs(j,u,step+1);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs(1,-1,0);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        puts((d[a]+d[b])%2?"Road":"Town");
    }
    return 0;
}

E. Shiritori

题目大意:

有一个字典中存在 N N N个单词,现在 T a k a h a s h i Takahashi Takahashi A o k i Aoki Aoki要用这个字典来玩游戏。游戏规则如下:

  • 两个人交替地说出单词,每次都是 T a k a h a s h i Takahashi Takahashi先开始。
  • 每次说出的单词的三个首字母必须与上一个单词的三个尾字母相同(有点像成语接龙),如果上一个人说的单词是 T a k a h a s h i Takahashi Takahashi,那么下一个人可以说 s h i p ship ship s h i e l d shield shield,但不能说 A o k i Aoki Aoki s i n g sing sing h i s his his
  • 字母是区分大小写的
  • 如果一个玩家没有能说的词,就视作该玩家输了
  • 玩家只能说在字典中存在的单词
  • 一个单词可以使用多次

假设双方都是采取最优策略,判断 T a k a h a s h i Takahashi Takahashi分别从所有单词开始游戏的胜负情况。

解题思路:

由于接龙的条件限制,我们很容易想到对于每个单词都可以看作是一条从当前单词三个首字母到达当前单词三个尾字母的边,所以我们可以将此问题转化为图论问题。

那么题目就可以转化为,问从每个顶点开始,每个人都采取最优策略,最后无路可走的人输掉比赛。

在这个问题中,我们可以发现以下性质:

  • 如果一个点没有任何出边,那么这个点就一定是先手必输点
  • 如果当前点只能走到先手必胜点,那么这个点就一定是先手必输点
  • 如果这个点可以走到一个先手必输点,那么这个点就一定是先手必胜点
  • 如果这个点既不是先手必输点也不是先手必胜点,那么走到这个点的时候就会以平局结束。

通过以上信息,我们就能得到解法。

首先,我们可以将所有不同的字母组合看做所有不同点,一共是 5 2 3 52^3 523个点。

其次,对于每条边记录一下开始点的出度,并建一个方向图。

然后由第一个性质可知,所有出度为0的点都是先手必输点,将这些点入队。再通过反向bfs来判断每个点是否能够走到先手必输点或者只能走到先手必胜点(判断第二个性质和第三个性质)

最后判断一下每条边的尾结点的胜负情况输出对应答案就可以了。

至于这里为什么不能用首节点来判断答案,因为当 T a k a h a s h i Takahashi Takahashi选择了第一个单词之后,他是无法选择接下来的走向, A o k i Aoki Aoki必然是从当前单词的尾结点开始,所以其实在 T a k a h a s h i Takahashi Takahashi选择了第一个单词之后,决策的权利是交给了 A o k i Aoki Aoki了的,所以胜负情况是尾结点来决定的。

代码:

#include
#include
#include
#include
#include
using namespace std;
typedef pair<int,int> PII;
const int N=52*52*52,M=2e5+10;
int n;
PII edge[M];
int h[N],e[M],ne[M],idx;
int d[N],ans[N];
int get(string s){
    int res=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='a'&&s[i]<='z') res=res*52+s[i]-'a'+26;
        else res=res*52+s[i]-'A';
    }
    return res;
}
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int main()
{
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=0;i<n;i++){
        string s;
        cin>>s;
        int a=get(s.substr(0,3)),b=get(s.substr(s.size()-3,3));
        d[a]++;//出度加一
        edge[i]={a,b};
        add(b,a);//建反图
    }
    queue<int> que;
    memset(ans,-1,sizeof ans);
    for(int i=0;i<N;i++){
        if(d[i]==0){//如果这个点出度为0的话,先手必输
            ans[i]=0;
            que.push(i);
        }
    }
    while(!que.empty()){
        int u=que.front();
        que.pop();
        for(int i=h[u];~i;i=ne[i]){
            int j =e[i];
            if(ans[j]==-1){
                d[j]--;
                if(ans[u]==0){//可以走到先手必输点
                    ans[j]=1;
                    que.push(j);
                }
                else if(d[j]==0){//只能走到先手必胜点
                     ans[j]=0;
                     que.push(j);
                }
            }
        }
    }
    for(int i=0;i<n;i++){
        if(ans[edge[i].second]==-1) puts("Draw");
        if(ans[edge[i].second]==0) puts("Takahashi");
        if(ans[edge[i].second]==1) puts("Aoki");
    }
    return 0;
}

F. Deforestation(插头dp 待补)

你可能感兴趣的:(AtCoder,图论,算法,dfs,树结构,动态规划)