2022-ICPC-杭州补题 (7/13) ACDFGKM

知识点整理 

A 数论,扩展欧几里得

C  三维背包

D  模拟签到

F 打表出规律

G 树哈希 基环树 拓扑排序

K 字典树

M  树剖,线段树维护gcd差分,换根dp , gcd推导

The 2022 ICPC Asia Hangzhou Regional Programming Contest 

https://codeforces.com/gym/104090

A. Modulo Ruins the Legend

time limit per test

1.0 s

memory limit per test

1024 MB

input

standard input

output

standard output

Grammy has a sequence of integers a1,a2,…,ana1,a2,…,an. She thinks that the elements in the sequence are too large, so she decided to add an arithmetic progression to the sequence. Formally, she can choose two non-negative integers s,ds,d, and add s+kds+kd to akak for each k∈[1,n]k∈[1,n].

Since we want to ruin the legend, please tell her the minimum sum of elements modulo mm after the operation. Note that you should minimize the sum after taking the modulo.

Input

The first line contains two integers n,mn,m (1≤n≤1051≤n≤105, 1≤m≤1091≤m≤109).

The second line contains nn integers a1,a2,…,ana1,a2,…,an (0≤ai

Output

Output exactly two lines.

The first line contains one integer, denoting the minimum sum of elements modulo mm.

The second line contains two integers s,ds,d (0≤s,d


【数论】扩展欧几里得

exgcd 内部

推导  a b已知, 系数x,y未知情况下

(1) gcd(a,b)= gcd(b,a) =gcd(b,a%b) 

(2) a%b= a- a/b * b 

(3) ax+by = gcd(a,b) = gcd(b,a%b) =bx1 + (a-a/b*b) y1 = bx1 + ay1 -a/b y1  b

提取a系数

ax= ay1

得出 x=y1

提取b系数

y= x1-a/b*y1

题目推导

sum + s*n + n*(n+1) /2 *d  =   ans 要求 ans mod m值最小

s ,d未知

已知其 gcd  ,设为 g 

则 s*n + n*(n+1)/2*d = k1 g 

sum + k1 g =  ans -k2 m   (即为 ans mod m之后的 ,设等价于减去了k2个m)

k1 g +k2 m= ans- sum 

已知g,m gcd,设为gg

k1 g+ k2m= kk gg 

为了让ans最小,考虑让 kk最小, -sum小于0,-sum/gg 时, kk*gg恰好为最小的大于等于-sum的数字,因题目保证有界,故kk*gg一定不会越界,这样ans得以求出,

反解 k1,k2

,再反解s,d即可

题目总结,本题突破点在于让答案mod m最小这一点上,这就要求我们必须把ans才成

ans-k m ,这样就变成了使ans最小且引入了新的变量 


C. No Bug No Game

time limit per test

1 second

memory limit per test

1024 megabytes

input

standard input

output

standard output

Putata is preparing the RPG Pro League (RPL) held by the International Computer Playing Company (ICPC). In this RPG game, the player will wear nn items at the same time. Each item can offer the player several points of power. There is a magic buff in the game, which can upgrade each item such that they can offer several points of bonus power.

However, the buff is limited, it can only buff at most kk points of power. Formally, assume the player is wearing nothing initially, and then will wear all the nn items one by one. The game server will scan through all these nn items one by one, according to the permutation that the player wears them. When the server is scanning the ii-th item, which can offer pipi points of power, let sum=∑1≤j

  • If sum+pi≤ksum+pi≤k, the whole item will be upgraded. The buff will offer wi,piwi,pi points of bonus power.
  • If sum≥ksum≥k, the item won't be upgraded. The buff will offer nothing.
  • Otherwise, only a part of the item will be upgraded. The buff will offer wi,k−sumwi,k−sum points of bonus power.

Putata is clever, he soon realized that he can adjust the permutation to wear these nn items to gain more points of bonus power! Unfortunately, Putata doesn't know the optimal permutation, please write a program to help him.

The behavior of the magic buff performed by the game server is a bug. The game code can work all thanks to bugs, so it is possible that wi,a>wi,bwi,a>wi,b where a

Input

The first line contains two integers nn and kk (1≤n≤30001≤n≤3000, 0≤k≤30000≤k≤3000), denoting the number of items and the limit kk.

Each of the following nn lines starts with an integer pipi (1≤pi≤101≤pi≤10), denoting the base power of the ii-th item, followed by pipi integers wi,1,wi,2,…,wi,piwi,1,wi,2,…,wi,pi (1≤wi,j≤1051≤wi,j≤105).

Output

Output a single line containing an integer, denoting the maximum points of total bonus power that can be reached. The base power is not included in the answer.


本题是物体装包问题,一个物体装入现有包的时候,如果能完全装下,就不能只装一部分,否则,在装入一部分可以的时候,要必须装满。思维突破点在于,某一物品装一部分可以,装全部无法装进去的情况,只会发生一次。这样我们设置dp[i][j][0/1]代表,前i个物品装了j体积,之前是否装过部分物品。这样就可以忽略掉排序问题。

细节方面,对于dp设置并不难想,但是dp的很多项细节确实是坑点,如初始状态必须是-无穷,状态承接,当前选育不选,答案统计,longlong等方面。

# include
using namespace std;

typedef long long int ll;

ll dp[3030][3030][2],w[3030][3030],p[3000+20];
int main ()
{
    int n,k;
    cin>>n>>k;
    for(int i=1; i<=n; i++)
    {
        cin>>p[i];
        for(int j=1; j<=p[i]; j++)
        {
            cin>>w[i][j];
        }
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=1;j<=k;j++)
        {

            dp[i][j][0]=dp[i][j][1]=-1e18;
        }
    }
    
    dp[0][0][0]=0;
    
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=k; j++)
        {

            dp[i][j][0]=max(dp[i][j][0],dp[i-1][j][0]);
            dp[i][j][1]=max(dp[i][j][1],dp[i-1][j][1]);
            if(j>=p[i])
            {
                dp[i][j][0]=max(dp[i][j][0],dp[i-1][j-p[i]][0]+w[i][p[i]]);
                dp[i][j][1]=max(dp[i][j][1],dp[i-1][j-p[i]][1]+w[i][p[i]]);
            }
            for(int now=1; now<=p[i]; now++)
            {
                if(j>=now)
                dp[i][j][1]=max(dp[i][j][1],dp[i-1][j-now][0]+w[i][now]);
            }
        }
    }
    ll ans=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=k; j++)
        {
            ans=max(ans,dp[i][j][0]);

            if(j==k)
            ans=max(ans,dp[i][j][1]);
        }
    }
    cout<


D. Money Game

time limit per test

1 second

memory limit per test

1024 megabytes

input

standard input

output

standard output

Putata and Budada are organizing a game with nn players. Each player has some deposit, which is a real number. Player ii has aiai deposits in the beginning. During each round of the game, the followings happen in order:

  • Player 11 gives player 22 half of player 11's deposit.
  • Player 22 gives player 33 half of player 22's deposit.
  • ...
  • Player n−1n−1 gives player nn half of player n−1n−1's deposit.
  • Player nn gives player 11 half of player nn's deposit.

The nn players played this game for exactly 2022120420221204 rounds. Putata wonders how much deposit each player has after the game. Please write a program to answer his question.

Input

The first line contains an integer nn (2≤n≤1052≤n≤105), denoting the number of players.

The second line contains nn integers a1,a2,…,ana1,a2,…,an (1≤ai≤1061≤ai≤106), denoting the deposit player ii has in the beginning.

Output

Output one line with nn real numbers, denoting the deposit each player has after they played this game.

Your answer will be considered correct if its absolute or relative error does not exceed 10−610−6. Formally, let your answer be aa, and the jury's answer be bb. Your answer will be considered correct if |a−b|max(1,|b|)≤10−6|a−b|max(1,|b|)≤10−6.

打个表就行,唯一注意点就是多保留位小数 

 
#include 

using namespace std;

typedef long long int ll;

double a[1000000+10];

int main()
{

    int n;
    cin>>n;
    double sum=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum+=a[i];
    }

    double ans=sum/(n+1);

    for(int i=1;i<=n;i++)
    {
        if(i==1)
        {
            cout<

F. Da Mi Lao Shi Ai Kan De

time limit per test

1.5 seconds

memory limit per test

1024 megabytes

input

standard input

output

standard output

Grammy has joined n+1n+1 QQ groups numbered 0∼n0∼n. Teacher Rice is in the group 00.

Every day, Grammy's friends send messages to some groups in 1∼n1∼n, and Grammy will select the messages which Teacher Rice likes and forward them to the group 00.

Here we define a message as a string consisting of lowercase letters, and Teacher Rice likes a message if and only if the string "bie" is a substring of the message.

Now, given the messages in groups 1∼n1∼n, Grammy will search messages from group 11 to group nn in order, checking the messages in the group one by one. For each message, pick out it if Teacher Rice likes it and it hasn't appeared in group 00, and forward it to group 00. Here appear means the same message has been forwarded to group 00 before.

Please output all the messages that Grammy will forward to group 00 in order. For each group, if Grammy can't pick out any message, output "Time to play Genshin Impact, Teacher Rice!" in one line.

签到题,直接模拟就行,阅读可能有些障碍。

#include 

typedef long long ll;

using namespace std;
int main()
{

    int t,n;

    cin >> t;
    map mp;
    while(t --)
    {
        int n;
        cin >> n;
        bool flag = false;
        for(int i = 1; i <= n; i ++)
        {
            string s;
            cin >> s;
            for(int j = 0; j < (int)s.length() - 2; j ++)
            {
                if(s[j] == 'b' && s[j + 1] == 'i' && s[j + 2] == 'e' && !mp[s])
                {
                    flag = true;
                    mp[s] ++;
                    cout << s << '\n';
                    break;
                }
            }
        }
        if(!flag)
            std::cout << "Time to play Genshin Impact, Teacher Rice!" << '\n';
    }
    return 0;
}

G. Subgraph Isomorphism

time limit per test

3 seconds

memory limit per test

1024 megabytes

input

standard input

output

standard output

Grammy wants to get the Turing Award! She decided to solve the Subgraph Isomorphism problem in polynomial time.

Since the problem is indeed too hard, she begins with doing some simplifications and trying to solve the simplified problem first.

Now Grammy has a connected undirected graph GG with nn vertices and mm edges. She wants to know whether it is possible to find a tree TT with nn vertices such that all connected subgraphs of GG with nn vertices and n−1n−1 edges are isomorphic to TT. Grammy knows the answer for sure, but she wants to give you a quiz.

Two graphs GG and HH are isomorphic if and only if there exists a bijection between the vertex sets of GG and HH (f:V(G)→V(H)f:V(G)→V(H)) such that any two vertices uu and vv of GG are adjacent in GG if and only if f(u)f(u) and f(v)f(v) are adjacent in HH.

Two vertices are adjacent if and only if they are directly connected by an edge.

图论题,全篇都在提树同构,而树同构就离不开树哈希。本题给的是图,图和树的关系,有比较热门的一点,那就是基环树。另外猜测结论,把问题引向方便解决的方向。

显然 m==n-1时,一定是一颗唯一的树,直接yes

m=n时,就是一颗基环树,且有唯一的环。而基环树最常见的操作,莫非破环成链。我们猜测只将环上点进行拆开,满足全部生成树同构,就能保证整个基环树的同构。

以环上每个点为跟跑一个树哈希。这样每个点就变成了一个具体的hash值,或许可以将其理解为字母a,b...

破环成链之后,aaaaa这种类型的,全部生成树都是相同的

ababababab这种,恰好也是完全同构的。所以就判断这两种即可。

对于树哈希,采用有根节点的树哈希

#include 

using namespace std;

typedef long long int ll;
typedef unsigned long long  ull;
const ull mask = std::chrono::steady_clock::now().time_since_epoch().count();
typedef pair HASH ;

vectorv[500000+10];
int du[500000+10],vis[500000+10];
queueq;
int fuck,nowzhong[2];
void dfs(int now,int pre)
{

    for(auto it:v[now])
    {
        if(it==pre)
            continue;
        if(it==fuck||vis[it]==1)
        {
            dfs(it,now);

        }
    }
}
ull shift(ull x)
{
    x^=mask;
    x^=x<<13;
    x^=x>>7;
    x^=x<<17;
    x^=mask;
    return x;
}

ull gethash(int now,int pre)
{

    ull nowhash=1;

    for(auto it:v[now])
    {
        if(it==pre)
            continue;
        if(it==fuck||(vis[it]==1))
        {
            nowhash+=shift(gethash(it,now));
        }
    }
    return nowhash;
}

vectorans;
void work(int root)
{
    fuck=root;

    nowzhong[0]=nowzhong[1]=root;


    HASH temp;
    temp.first=gethash(nowzhong[0],0);


    if(nowzhong[1])
    {
        temp.second=gethash(nowzhong[1],0);
        if(temp.first>t;

    while(t--)
    {
        int n,m;
        cin>>n>>m;


        for(int i=1; i<=n; i++)
        {
            v[i].clear();
            du[i]=0;
            vis[i]=0;

        }

        ans.clear();

        while(!q.empty())
            q.pop();

        vectortempfuck;

        for(int i=1; i<=m; i++)
        {
            int x,y;
            cin>>x>>y;

            tempfuck.push_back((char)(x+'0'));
            tempfuck.push_back((char)(y+'0'));

            v[x].push_back(y);
            v[y].push_back(x);
            du[x]++;
            du[y]++;
        }
        if(m==n-1)
        {
            cout<<"YES"<n)
        {
            cout<<"NO"<

K. Master of Both

time limit per test

1.0 s

memory limit per test

1024 MB

input

standard input

output

standard output

Professor Hui-Bot is the master of string theory and advanced data structures, so he came up with an interesting problem. Given a sequence of nn strings consisting of only lowercase English letters, how many inversions are there in this sequence when the strings are compared by lexicographical order?

As the most extraordinary student of Hui-Bot, Putata and Budada mastered superb string theory and advanced data structure skills respectively, and they solved this problem together with ease. However, there are qq different parallel universes, where the characters in the alphabet are not appearing in the original order.

Formally, the alphabet in each universe is a string, which is a permutation of the 2626 lowercase English letter, denoting the order each character appears.

A string aa is lexicographically smaller than a string bb if and only if one of the following holds:

  • aa is a prefix of bb, but a≠ba≠b;
  • in the first position where aa and bb differ, the string aa has a letter that appears earlier in the alphabet than the corresponding letter in bb.

The number of inversions in a sequence aa of length nn is the number of ordered pairs (i,j)(i,j) such that 1≤i

字典树,首先肯定要想到预处理。根据每次给出的字典序顺序,我们暴力枚举字母i,j参生的贡献,且贡献只产生在i的字典序大于j,但i出现在j前面的时候。

值得注意的是,我们统计的是字符串的逆序对。字符串产生逆序对的时候,当且仅当两种情况,一种是,前缀相同,出现一对不相同字母,一种是,一个字符串是另一个的前缀。

对于第一种情况,前缀相同很自然联系到字典树,我们把字符串每个字母按照位置顺序插入到字典树里面,在插入当前字母ch之前,统计当前前缀结尾不是ch的x,记录cnt[x][ch]总数即可。

而第二种,我们可以设置一个最小的通配符。在字符串末尾,这样就保证了一定可以产生不等的情况(除非二者完全相等) 

# include
using namespace std;

typedef long long int ll;
ll tree[2000000+10][30];
ll sum[2000000+10][30],cnt[30][30];

int pos,tot;
char s[2000000+10];
void add(int len)
{

    pos=0;


    for(int i=1; i<=len+1; i++)
    {
        int temp=0;
        if(i<=len)
         temp=(int)(s[i]-'a'+1);


        for(int j=1; j<=26; j++)
        {
            if(j==temp)
                continue;
            cnt[j][temp]+=sum[pos][j];

        }
        sum[pos][temp]++;

        if(tree[pos][temp]==0)
        {
            tot++;
            tree[pos][temp]=tot;
        }
        pos=tree[pos][temp];
    }
}
int fuck[100];

int main ()
{
    int n,m;
    cin>>n>>m;

    for(int i=1; i<=n; i++)
    {
        scanf("%s",s+1);
        int len=strlen(s+1);
        add(len);
    }

    while(m--)
    {
        string ss;
        cin>>ss;

         ss=" "+ss;
        for(int i=1; ifuck[j])
                {
                    ans+=cnt[i][j];
                }
            }
        }

        cout<

M. Please Save Pigeland

time limit per test

3 seconds

memory limit per test

1024 megabytes

input

standard input

output

standard output

Some horrible disease called Mysterious Oscar is spreading in Pigeland. The Pigeland has nn cities and is connected by n−1n−1 bi-directional roads. The ii-th road is connecting city uiui and city vivi, with length wiwi. It is guaranteed that for every city it is possible to travel to any other city using these roads.

Now, there are kk distinct cities c1,c2,…,ckc1,c2,…,ck having the horrible disease. As the leader of the Pigeland, Putata and Budada are going to save the Pigeland. First they will find a city rr to build up a hospital, without considering whether city rr is infected by the disease. Then, they will use the rest of their money to build the only vehicle which could travel in Pigeland, the Pigpigcar. Because they are strapped for cash, they can only build one Pigpigcar, and once a Pigpigcar is built, some parameter dd of the Pigpigcar is set, and could not be changed. The Pigpigcar having parameter dd can only travel from one city to the other, where the distance between the two cities should be a multiple of dd. Formally, if you want to travel from city uu to city vv, the distance of the shortest path between u,vu,v should be p×dp×d, where pp is a non-negative integer, and this would cost pp Pigecoins. Please notice that if you want to travel from city uu to city vv, it is not necessary to stop at every city on the path from uu to vv, the Pigpigcar can directly go from uu to vv, if the minimum distance between uu and vv is a multiple of dd.

For the following kk days, Putata and Budada will take the following actions to save the Pigeland. On the ii-th day, they will travel to city cici from city rr along the shortest path between city rr and city cici using the Pigpigcar, cure all the piggies in the city and then travel back to city rr from city cici.

首先最朴素办法就是暴力枚举每个点为起点时的代价。但发现这样可以用换根DP来写,这样我们就预处理出来每个点为起点的路程和。再考虑用一种数据结构快速维护gcd的变化。

gcd(a,b,c) = gcd( a, gcd(b,c) ) = gcd (a, gcd(b,c-b) ) = gcd( gcd(a, b) ,c-b) =gcd(a,b-a,c-b)

根据这条性质,我们维护一个差分数组,求全部的gcd,就是求差分数组的gcd,线段树来维护。且我们发现,在换根的时候,被换到的节点的儿子,若包含c节点,则其全部c节点距离都会减少换根直接牵扯到的边权。而我们无法对凌乱的儿子们做暴力筛选,考虑书剖思想,把dfn序作为线段树上点排序的标准,这样一个节点的儿子,就对应了线段树上连续的区间。

#include 
#pragma GCC optimize(2)
using namespace std;
typedef long long int ll;
ll gcdd[500000*4+10];
ll dfn[500000*4+10],bookc[500000*4+10];

struct node
{
    int b,e;
    ll val;
};

struct node s[1000000+10];
int f[1000000+10],nex[1000000+10];
int lenn;
void add(int x,int y,ll z)
{

    s[lenn].b=x;
    s[lenn].e=y;
    s[lenn].val=z;
    nex[lenn]=f[x];
    f[x]=lenn;
    lenn++;

}
ll dissum[500000+10],dis[500000+10],cnt[500000+10];
ll c[500000+10],timecnt;
ll dp[500000+10];
ll b[500000+10],a[500000+10];
ll minn[5000000+10],maxx[500000+10],m,minnid[500000+10],maxxid[500000+10];
int len;
int pos[500000+10];
void build(int root,int l,int r)
{
    if(l==r)
    {
        gcdd[root]=b[l];
        return ;
    }
    int mid=(l+r)>>1;

    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);

    gcdd[root]=__gcd(gcdd[root<<1],gcdd[root<<1|1]);

}
void change(int root,int l,int r,int pos,ll val)
{
    if(l==r)
    {
        gcdd[root]+=val;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
        change(root<<1,l,mid,pos,val);
    else
        change(root<<1|1,mid+1,r,pos,val);

    gcdd[root]=__gcd(gcdd[root<<1],gcdd[root<<1|1]);
}

void dfs1(int now,int pre)
{
    timecnt++;
    dfn[now]=timecnt;

    cnt[now]=bookc[now];
    if(bookc[now])
    {
        len++;
        a[len]=now;
    }

    minn[now]=1e9;
    maxx[now]=0;
    if(bookc[now])
    {
        minn[now]=dfn[now];
        maxx[now]=dfn[now];
    }
    int x=f[now];


    while(x!=-1)
    {
        int j=s[x].e;
        if(j==pre)
        {
            x=nex[x];
            continue;
        }

        dis[j]=dis[now]+s[x].val;

        dfs1(j,now);

        dissum[now]+=(dissum[j]+cnt[j]*s[x].val);

        cnt[now]+=cnt[j];
        x=nex[x];

    }
}
void dfs2(int now,int pre,ll val)
{
    if(now!=1)
    {
        ll tempsum=dp[pre]-dp[now];
        ll tempcnt=cnt[now]*val;
        tempsum-=tempcnt;
        ll fuckcnt=m-cnt[now];
        fuckcnt*=val;
        dp[now]+=tempsum+fuckcnt;

    }
    maxx[now]=0;
    minn[now]=1e9;
    if(bookc[now])
    {
        maxx[now]=dfn[now];
        minn[now]=dfn[now];
        maxxid[now]=pos[dfn[now]];
        minnid[now]=pos[dfn[now]];
    }

    int x=f[now];
    while(x!=-1)
    {
        int j=s[x].e;
        if(j==pre)
        {
            x=nex[x];
            continue;
        }
        dfs2(j,now,s[x].val);

        if(maxx[now]minn[j])
        {
            minn[now]=minn[j];
            minnid[now]=minnid[j];
        }
        x=nex[x];
    }
}

bool cmp(int x,int y)
{
    return dfn[c[x]]minn[j])
        {
            minn[now]=minn[j];
            minnid[now]=minnid[j];
        }
        x=nex[x];
    }
}
int main()
{

    int n;
    cin>>n>>m;

    memset(f,-1,sizeof(f));

    if(m==1)
    {
        cout<<0;
        return 0;
    }
    for(int i=1; i<=m; i++)
    {
        scanf("%lld",&c[i]);
        bookc[c[i]]=1;
    }

    for(int i=1; i

你可能感兴趣的:(ICPC区域赛真题,算法)