Codeforces Round #677 (Div. 3)

A. Boring Apartments

题意:

存在一个有规律的数列1,11,111,1111,2,22,222,2222...9,99,999,9999

给定数列中的某个数x,询问该数(包括)前有几个数。

思路:

取出这个数的最后一位x,答案就是(x*4+当前数的位数)

时间复杂度:   O(logn)

 AC代码:

void solve()
{
    int n;
    cin>>n;
    int k = n%10;
    int cnt = 0;
    while(n)
    {
        n/=10;
        cnt++;
    }
    cout<<(k-1)*10+(1+cnt)*cnt/2<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

 B. Yet Another Bookshelf

题意:

        给出一个长度是n,由若干个01组成的数列 

现在可以执行这样的操作:

  •         选择一段连续的区间l,r,(该区间内的数必须全为1),我们可以将这一整段向右或者向左移动一个单位。

最少几次这样的操作可以使数组内全部的1聚集在一起。

思路:

贪心地选择从左向右依次合并即可。

第一个1所在的位置s,最后一个1所在的位置t

最少次数就是s~t中0的个数 

 时间复杂度: O(n)

AC代码:

int a[N];
 
void solve()
{
    int n;
    cin>>n;
    int s = -1,t = -1;
    rep(i,n)
    {
        cin>>a[i];
        if(a[i]==1&&s==-1)s=i;
    }
    for(int i = n ; i > 0 ; i--)
    {
        if(a[i]==1)
        {
            t = i;
            break;
        }
    }
    int res = 0;
    for(int i = s; i <= t ; i++)
    {
        if(a[i]==0)res++;
    }
    cout<<res<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}
 

C. Dominant Piranha

题意:

        给定一个长度为n的数列,一个数可以合并一个相邻的更小数,合并后当前数加1,询问是否存在一个数可以将全部数合并,如果存在就输出这个数的下标,否则输出-1

思路:

 显然,只有一种情况不存在,就是所有数相同

否则,找到下标index,该下标的数为最大值,且左右两边的数至少有一个可以合并。

时间复杂度: O(n)

AC代码:

int a[N];
 
void solve()
{
    int n;
    cin>>n;
    rep(i,n)cin>>a[i];
    int maxn = -1;
    rep(i,n)
    {
        if(a[i]>maxn)
        {
            maxn = a[i];
        }
    }
    for(int i = 1 ; i < n ; i++)//下面是找index的过程,第二个循环不必要这么写,不改了
    {
        if(a[i]==maxn&&a[i+1]!=maxn)
        {
            cout<<i<<endl;
            return;
        }
    }
    for(int i = n ; i > 1 ; i--)//可以直接由一个if替代
    {
        if(a[i]==maxn&&a[i-1]!=maxn)
        {
            cout<<i<<endl;
            return;
        }
    }
    cout<<-1<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}
 

 D. Districts Connection

 题意:

        现在有n个点,每个点有一个权值,不同权值的点直接可以连一条边。

        是否存在n-1条边将所有点连通(生成树)

        存在则输出方案,不存在则输出NO

思路:

暴力遍历数组,找到每个点可以与哪些点连边,点之间的连通性用并查集维护

时间复杂度        O(n^2)       (2<=n<=5000)

AC代码:

int p[N],a[N];
vector<PII> res;//存下最后的答案
 
int find(int x)//并查集
{
    return p[x]==x?p[x]:p[x] = find(p[x]);
}
 
void solve()
{
    int n;
    cin>>n;
    res.clear();
    rep(i,n)cin>>a[i],p[i] = i;
    int cnt = 0;
    for(int i = 1 ; i < n ; i++)//两层for循环暴力枚举所有可能的边
    {
        for(int j = i + 1 ; j <= n ; j++)
        {
            if(a[i]!=a[j])//如果可以连通
            {
                int x = find(i),y = find(j);
                if(x!=y)//如果这两个点尚未连通,则合并
                {
                    p[x] = y;
                    cnt++;
                    res.push_back({i,j});//同时储存答案
                }
            }
            if(cnt==n-1)//连接了n-1条边后程序可以提前结束
            {
                cout<<"YES"<<endl;
                for(auto val : res)
                {
                    cout<<val.first<<" "<<val.second<<endl;
                }
                return;
            }
        }
    }
    cout<<"NO"<<endl;//找不到方案
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}
 

E. Two Round Dances 

 题意:

        现有n个人,分别编号1n,现在要将这n个人分成两组,这两组人会围成一个圈,现在询问有几种分法。

        由于最后会围成圈子,所以[1,2,3],[2,1,3],[2,3,1]被认为是同一种。

思路:

n个数的全排列有n!种,设初始答案为res = n!

由于最后分成的两组在第1组或是第2组没有区别,所以答案要除以2

由于会围成圈子,易得n个人的圈子有n个重复

最后每组有n/2个人,每一组都有n/2个重复

最后答案就是res = n! / 2 / (n / 2) / (n / 2)

时间复杂度: O(n)

AC代码:

void solve()
{
    int n;
    cin>>n;
    ull res = 1;
    for(int i = 1 ; i <= n ; i++)
    {
        res = res * i;
    }
    res = res/2/(n/2)/(n/2);
    cout<<res<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
//    int t;
//    cin>>t;
//    while(t--)
    {
        solve();
    }
    return 0;
}
 

F. Zero Remainder Sum 

 题意:

        有一个n*m的矩阵,在每一行至多可以选取m/2(向下取整)个数,取出的数和为sum,给定数k,要求sum%k==0

        询问sum最大为多少。

思路:

对于矩阵中的每个点,都有选和不选两种情况,再次基础上做记忆化搜索,dfs一遍即可。

(也可以用dp)

时间复杂度: O(n*k*(m^2))

AC代码:

 代码中ans数组表示的意思是

以x,y为起点,在当前行选了cnt个数,而且此时和sum与k的模数为mod的情况下开始搜索,满足题目条件的最大值为ans[x][y][cnt][mod]

const int N = 80,M = N * N,MAXN = 1,INF = 0x3f3f3f3f,P = 1e9+7;
 
int ans[N][N][N][N];
int g[N][N];
int n,m,k;
 
int dfs(int x,int y,int cnt,int mod)
{
    if(x>n||y>m||x<1||y<1)//越界(也表示当前情况搜索完成)
    {
        if(mod == 0)return 0;//如果mod==0,那么搜索到这个点的路径是符合题目要求的,返回0
        return -INF;//反正,这个路径的和不符合题目要求,返回负无穷
    }
    if(ans[x][y][cnt][mod]!=-1)return ans[x][y][cnt][mod];//如果这种情况已经搜索过
    int temp = -INF;
    temp = max(temp,dfs(x+1,1,0,mod));//这两种表示不选当前数
    temp = max(temp,dfs(x,y+1,cnt,mod));
    if(cnt < m / 2)//如果可以选择,则选择当前数
    {
        temp = max(temp,dfs(x+1,1,0,(mod+g[x][y])%k)+g[x][y]);
        temp = max(temp,dfs(x,y+1,cnt+1,(mod+g[x][y])%k)+g[x][y]);
    }
    return ans[x][y][cnt][mod] = temp;//记忆化答案
}
 
void solve()
{
    memset(ans,-1,sizeof ans);
    cin>>n>>m>>k;
    rep(i,n)//这里我宏定义了for循环for(int i = 1 ; i <= n ; i++)
        rep(j,m)
            cin>>g[i][j];
//    rep(i,n)
//    {
//        rep(j,m)
//        {
//            cout<<g[i][j]<<" ";
//        }
//        cout<<endl;
//    }
 
    cout<<dfs(1,1,0,0)<<endl;//开始搜索
//    rep(i,n)
//    {
//        rep(j,m)
//        {
//            cout<<ans[i][j][0][0]<<" ";
//        }
//        cout<<endl;
//    }
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
//    int t;
//    cin>>t;
//    while(t--)
    {
        solve();
    }
    return 0;
}

G. Reducing Delivery Cost 

 题意:

        给定一张由n个点和m条无向边组成的图,每条边都有一个权值w,现在有k条路径,每条路径有一个起点s和终点t,现在可以选择至多1条边,将这条边的权值重置为0,使得k条路径的权值和最小。

  • 对于每一条路径,都有最短路d,选择重置一条边,现在要使d1+d2+...dk最小。

思路:

由于本题中的图是个稀疏图,考虑用spfa跑多源最短路(用堆优化Dijkstra应该也可以)

删掉某一条边(u,v)后,对于某一路径的最短距离一定有

d = min(dist[s][t],dist[s][u]+dist[v][t],dist[s][v]+dist[u][t])

所以最后遍历删除每一条边,最后取最小即可。

时间复杂度:

  • O(m*(k + n ^ 2))     (该题常数较小,可以通过)
  • (堆优化Dijkstra)O((n^2)*logn+mk)

AC代码: 

const int N = 1e3+10,M = 2 * N,MAXN = 1,INF = 0x3f3f3f3f,P = 1e9+7;
 
struct node{
    int first,second;
}query[N],edge[N];//存下所有的边和路径
 
int h[N],e[M],ne[M],w[M],idx;
int que[N],tail,head;
bool book[N][N];
int dist[N][N];
int n,m,k;
 
 
void add(int a,int b,int c)
{
    e[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx++;
}
 
void spfa(int s)//spfa跑最短路
{
    tail = 0,head = 0;
    que[tail++] = s;
    for(int i = 1 ; i <= n ; i++)dist[s][i] = INF;
    dist[s][s] = 0;
    book[s][s] = true;
    while(head!=tail)
    {
        int temp = que[head++];
        book[s][temp] = false;
        if(head==N)head = 0;
        for(int i = h[temp] ; ~i ;i = ne[i])
        {
            int u = e[i];
            if(dist[s][u]>dist[s][temp]+w[i])
            {
                dist[s][u] = dist[s][temp] + w[i];
                if(!book[s][u])
                {
                    que[tail++] = u;
                    book[s][u] = true;
                    if(tail==N)tail = 0;
                }
            }
        }
    }
}
 
 
void solve()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>k;
    for(int i = 1 ; i <= m ; i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edge[i] = {a,b};
        add(a,b,c),add(b,a,c);
    }
    rep(i,n)spfa(i);//每个点都跑一遍最短路
    rep(i,k)
    {
        int a,b;
        cin>>a>>b;
        query[i] = {a,b};
//        for(int i = 1 ; i <= n ; i ++)
//        {
//            cout<<dist[a][i]<<" ";
//        }
//        cout<<endl;
    }
    int res = INF;
    for(int i = 1 ; i <= m ; i++)//遍历删除所有边
    {
        int u = edge[i].first,v = edge[i].second;
        int temp = 0;
        for(int j = 1 ; j <= k ; j++)//删除第i条边后的sum
        {
            int s = query[j].first,t = query[j].second;
            temp += min(dist[s][t],min(dist[s][u]+dist[v][t],dist[s][v]+dist[u][t]));
        }
        res = min(temp,res);//取最小值
    }
    cout<<res<<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
//    int t;
//    cin>>t;
//    while(t--)
    {
        solve();
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值