上海理工大学第二届“联想杯”全国程序设计邀请赛题解

上海理工大学第二届“联想杯”全国程序设计邀请赛

比赛链接:https://ac.nowcoder.com/acm/contest/17574

A. A-SOUL!

题意:给出n个数,与公差d。问将n个数重新排列后最长的等差数列的长度是多少。

思路:从小到大排序后用map记录并维护当前数结尾的等差数列长度。

#include<bits/stdc++.h>
#include<sstream>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
const int maxn = 1e5+10;

LL arr[maxn];

int main()
{
    memset(arr, 0  ,sizeof arr);
    LL n  ,k;
    cin >> n >> k;
    for(int i=0;i<n;++i)
    {
        cin >> arr[i];
    }
    sort(arr, arr+n);
    map<LL , LL> rec;
    LL ans = 1;
    rec[arr[0]] = 1;
    for(int i=1;i<n;++i)
    {
        LL tem = arr[i];
        rec[tem] = max(1LL, rec[tem]);
        if(rec.count(tem-k))
        {
            rec[tem]  = max(rec[tem]  ,rec[tem-k]+1 );
        }
        ans = max(ans, rec[tem]);
    }
    cout << ans;

}

B. Bheith i ngra le

题意:一个n*m的网格,颜色为白或者黑。我们定义当前网格为好。

1.所有黑色的方格为一个联通块

2.对于每一列所有黑色方格的最下端的方格必须是黑色,且该列所有黑色方格必须相连。

我们定义在列i的最高的黑色方格为\(h_i\)。我们定义一个好的网格图为一个山峰,当该网格至少存在一个整数对(l,r)满足

  1. 1 \(\leq\)l\(\leq\)r\(\leq\)n
  2. 所有的\(h_i\)(1 \(\leq\) i \(\leq\) l) 是非严格单调递增
  3. 所有的\(h_i\)(r \(\leq\) i \(\leq\) n)非严格单调递减
  4. 所有的\(h_i\) (l \(\leq\) i \(\leq\) r)相同

输出n*m的网格中总共可以画几座山,结果mod \(10^9+7\)

思路:

DP[i][j]= \(\sum_{x=1}^n\)DP[i-1][x] * (n+1-x) // O(\(n^2\)*m)

​ = DP[i][j-1] + \(\sum_{x=1}^j\) ( DP[i-1][x] * (n+1-x) ) //用前缀和后 O(n*m)

ans = \(\sum_{i=1}^n\)\(\sum_{j=1}^m\) ( DP[i][j] * (n+1-j) ) ;

DP[i][j]为底座宽度为j,高度为i的山的种类。

比如DP[3][2] = 5;有5种

底座长度为3高度为3的山的形状种类DP[3][3] =DP[2][1] * 3 + DP[2][2] * 2 + DP[2][1] * 1,即在长度3的底座长度首先上摆放高度为2底座长度为1的山。同时因为该山底座长度为1,在长度为3的底座上有左、中、右三种摆法。同理高度为2底座长度为2的山有两种摆法,高度为2长度为3的山只有一种摆法。

因此 DP[i][j] = \(\sum_{x=1}^j\)DP[i-1][x] * (j+1-x); 此时时间复杂度为 O(\(n^2*m\)),DP方程可以优化为DP[i][j] = DP[i][j-1] + \(\sum_{x=1}^j\) DP[i-1][x] 用前缀和优化表示

\(\sum_{x=1}^j\) DP[i-1][x],时间复杂度变为了 O(n*m);

当跑到DP[n][m]后,我们以及得到了在画布内可以画出的所有山的形状的数量。将画布视为长度为n的底座, 每种底座长度为j的山,在画布上从最左侧摆到最右侧都有(n+1-j)种摆法方法。

ans = \(\sum_{i=1}^n\)\(\sum_{j=1}^m\) ( DP[i][j] * (n+1-j) ) ;

代码:

#include<bits/stdc++.h>
#include<sstream>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 2e3+10;
const int MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;



LL DP[maxn][maxn];
LL pre[maxn];

void solve()
{
    int n, m;cin >>n >> m;
    memset(pre, 0, sizeof pre);
    LL ans  = 0;
    for(int i=1;i<=m;++i)
    for(int j=1;j<=n;++j)
    {
        if(j==1)
        {
            DP[i][j] = 1;
            pre[j] = 1;
        }
        else
        {
            DP[i][j] = (DP[i][j-1]+pre[j]) % MOD;
            pre[j] = (DP[i][j]+ pre[j-1])%MOD;
        }
        ans  = (ans%MOD  + (DP[i][j] * (n+1-j)) % MOD) % MOD;
    }
    printf("%lld" , (ans+1)%MOD);

}

int main()
{
    ios::sync_with_stdio(false);
    solve();

}

C.Counting Cats!

题意:给出一系列字符串,"c" "a" "t" "ca" "at" "cat"有用,问最多这些字符串可以拼接出几个"cat"字符串。

思路:直接匹配

代码:

#include<bits/stdc++.h>
#include<sstream>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 1e5+10;
const int MOD = 998244353;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;


int rec[10];

void solve()
{
    int n ;cin >> n;
    int ans  = 0;
    for(int i=0;i<n;++i)
    {
        string x;cin  >> x;
        if(x=="c") rec[0]++;
        if(x=="a") rec[1]++;
        if(x=="t") rec[2]++;
        if(x=="ca") rec[3]++;
        if(x=="at") rec[4] ++;
        if(x=="cat") ans++;
    }
    int tem= INF;
    ans+=min(rec[0] , rec[4]);
     tem = min(rec[0] , rec[4]);
    rec[0]-=tem;
    rec[4]-=tem;
    tem = INF;
    tem = min(rec[2], rec[3]);
    ans+=tem;
    rec[2]-=tem;
    rec[3]-=tem;
    ans+=min(rec[0], min(rec[1], rec[2]));
    printf("%d", ans);

}

int main()
{
    ios::sync_with_stdio(false);
    solve();

}

D.Dahno Dahno

题意: 给出N个物品,你要将物品分成两A类,B类。每类都不能为空。当物品i,j在同一类中时会产生\(v_{i,j}\) + \(v_{j, i}\) 的能量,求最大的能量值.

思路:先假设可以得到所有能量,所有能量减去无向图最小割即可。无向图最小割Stoer_Wagner算法模板题。

#include<bits/stdc++.h>
#include<sstream>
#pragma GCC optimize(2)
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(LL i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<LL, LL> P;
const LL maxn = 5e2+5;
const LL MOD = 1e9+7;
const LL INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

LL G[maxn][maxn];  //临接矩阵表示边,时间复杂度为O(n^3),因此空间一定放的下
LL f[maxn];        // v[i]代表节点i合并到的顶点
LL w[maxn];        //    定义w(A,x) = ∑w(v[i],x),v[i]∈A
bool vis[maxn];     // 标记该点是否加入A集合。
LL squ[maxn];      //记录移除的节点次序,用于输出两个割的内容
LL Index ;          //标记最后一次更新最小割的位置,作为两个割的分界线

LL Stoer_Wagner(LL n)
{
    LL min_cnt = INF, r =  0;
    for(LL i  = 0; i < n; ++i)
    {
        f[i] = i;//    初始还未合并,都代表节点本身
    }
    while(n > 1 )
    {
      //  printf("TEST1\n");
        LL pre  = 0 ;//    pre用来表示之前加入A集合的点(在t之前一个加进去的点)
        memset(vis,  0 , sizeof  vis);
        memset(w, 0 , sizeof w);
        for(LL i = 0 ; i < n -1 ;++i)
        {
            //printf("TEST2\n");
            LL k  = -1;
            for(LL j = 1 ; j < n; ++j)
            {
                if(!vis[f[j]])
                {
                    w[ f[j] ] += G[ f[pre] ][f[j]];
                    if(k == -1 || w[f[k]] < w[f[j]])
                    {
                        k  =j;
                        LL x = x;
                    }
                }
            }

            vis[f[k]] = true;       //标记该点x已经加入A集合
            if(i==n-2)              //若|A|=|V|(所有点都加入了A),结束
            {
               // printf("pre = %lld  k  =%lld \n", pre, k);
                const LL u = f[pre] , v = f[k];    // 令倒数第二个加入A的点(v[pre])为s,最后一个加入A的点(v[k])为t
                //printf("%lld ----> %lld \n" , v ,u); //v 向 u 合并
                squ[r++] = v;
                if(w[v] < min_cnt)//    则s-t最小割为w(A,v),用其更新min_cut
                {
                    min_cnt = w[v];
                    Index = r;
                }
                for(LL j  = 0; j <  n ; ++j)//    merge(u, v)
                {
                    G[u][f[j]] += G[f[j]][v];
                    G[f[j]][u] += G[f[j]][v];
                }
                f[k] = f[--n];
            }
            pre = k;
        }
    }
    return min_cnt;
}

void solve()
{
    LL n ;cin >> n;
    memset( G,  0, sizeof G);
    LL ans = 0;
    for(LL i=0;i<n;++i)
    {
        for(LL j=0 ; j < n; ++j)
        {
            LL x; cin >> x;
            ans += x;
            G[i][j] +=x;
            G[j][i] +=x;
        }
    }
    //printf("归并的步骤为:\n");
    LL res = Stoer_Wagner(n);
//    printf("最小割为%lld \n 图划分部分为A:" , res);
//    for(LL i=0; i < n;++i)
//    {
//        if(i==Index)
//            printf("部分B:");
//        printf("%lld ", squ[i]);
//    }
//    printf("\n");
    printf("%lld", ans - res);

}

int main()
{
    ios::sync_with_stdio(false);
    solve();
    return 0 ;
}

E.Experiment Class

题意:给出点A , B, C, D 和原点O(0, 0)都在第一象限且A , B 不和原点重合.问从C出发经过射线OA, OB后到达点D的最短距离.

思路:首先找出OA, OB中斜率大(小)的射线记为:UP(down),和OC,OD中斜率较大(小)的点记为:P_up,(p_down)。现在有四种情况。

1.两点都在OA,OB所成夹角之外,直接连接CD即为最短距离。

2.P_up在夹角内,P_downD在夹角外。此时做P_up关于射线UP的对称点,连接对称点和P_down即为最小距离。

3.P_down在角内,P_up在角外。同理做P_dow关于射线DOWN的对称点,连接对称点和P_up。即为最小距离。

4.二者都在角内,此时做P_up关于射线UP的对称点,做P_dow关于射线DOWN的对称点,连接二个对称点即为最小距离。

最后我要吐槽官方题解点在角内,角外写反了

大佬们的优美代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
typedef long long ll;
using namespace std;
using namespace std;
void get(double a, double b, double& x, double& y)//求点关于直线的对称点。
{
    // a b c为直线的一般式参数
    //公式 x'=((b*b-a*a)*x-2*a*b*y-2*a*c)/(a*a+b*b);
    //      y'=((b*b-a*a)*y-2*a*b*x-2*b*c)/(a*a+b*b);
    double x1 = ((b * b - a * a) * x - 2 * a * b * y ) / (a * a + b * b);
    double y1 = ((a * a - b * b) * y - 2 * a * b * x ) / (a * a + b * b);
    x = x1, y = y1;
}
int main()
{
    double a, b, c, d, x0, y0, x1, y1;
    scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &x0, &y0, &x1, &y1);
    if (b * c < a * d)swap(a, c), swap(b, d);//两条河边按照斜率排序
    if (y0 * x1 < y1 * x0)swap(x0, x1), swap(y0, y1);//两个点按照斜率排序
    if (b * x0 > y0 * a)get(-b, a, x0, y0);//把上面那个点翻到角外面
    if (c * y1 > x1 * d)get(-d, c, x1, y1);//把下面那个点翻到角外面
    printf("%.3lf\n", sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)));//两点之间直线最短
    return 0;
}

我补题时丑陋的模板代码:

#include<bits/stdc++.h>
#include<sstream>
#pragma GCC optimize(2)
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long Uint;
typedef pair<int, int> P;
const int maxn = 5e2+5;
const int MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

struct Point
{
    double x, y;
    Point(double x = 0 , double y = 0) : x(x) , y(y) {}
};


typedef Point Vector;

Vector operator + (Vector A, Vector B)
{
    return Vector(A.x + B.x , A.y + B.y);
}

Vector operator - (Vector A, Vector B)
{
    return Vector(A.x - B.x , A.y - B.y);
}


Vector operator * (Vector A, double p)
{
    return Vector(A.x * p , A.y * p);
}

struct Line
{
    Point v, p;

    Line(Point v, Point p) : v(v) , p(p) {}
    Point Point(double t)
    {
        return v + (p-v)*t;
    }
};






double Dot(Vector A, Vector B)
{
    return A.x * B.x + A.y  *B.y;
}

Point GetLineProjection(Point P, Point A, Point B)
{
    Vector v = B - A;
    return A + v * ( Dot(v, P - A) / Dot(v, v));
}



Point GetLineSymmetry(Point a, Line b)
{
    Point c  = GetLineProjection(a, b.p, b.v);
    return a + ((c - a)*2);
}




void solve()
{
    Point  b , c ,d ,e;
    Point start;
    cin   >> b.x >>b.y >>c.x >> c.y >>d.x >>d.y >>e.x >>e.y;
    Line up = Line(start , b)  , down = Line(start ,c);
    if(up.p.y*down.p.x <= up.p.x*down.p.y) swap(up, down);
    if(d.y*e.x <= d.x*e.y) swap(d, e);
    if(up.p.y*d.x > up.p.x*d.y)
    {
        d =  GetLineSymmetry(d , up);
    }
    if(down.p.y*e.x < down.p.x * e.y)
    {
        e  =GetLineSymmetry(e, down);
    }
    Vector ans =  d-e;
    printf("%.3lf", sqrt(Dot(ans , ans)));
}


int main()
{

    ios::sync_with_stdio(false);
    solve();
    return 0 ;
}

I. Identical Day

题意:给出一串01串和k,当存在一段连续的1长度为n.这段1的UNHAPPY值为n *(n+1)/2.给出01串的UNHAPPY值为所有连续的1的UNHAPPY值之和记为 ALL。现在你可以用一次操作将01串中的1变为0.求使用最少的操作次数使得给出01串的ALL\(\leq\) k。

​ 最开始我想到的是每次找到最长的1,然后将中间的1变为0.但是11111按照我的思路两次操作后变为10011,显然正确的操作是变为10101。

​ 正确的方法我们先定义(L, X)是将长度为L的1串进行X-1次操作,平均分为X段1之后获得的UNHAPPY值。首先我们要证明有 (L, X) - (L,X+1) \(\geq\) (L,Y)-(L,Y+1) 当(X< Y).(但是这个我整不出来).对于01串中每一段连续1,将一次操作后ALL的减少值(L, X+1) - (L, X) (1 \(\leq\) X \(\leq\) L)存入优先队列。然后我们每次取队列中最大的数就是当前的最优的ALL减少值,此时一次操作所能获得的最大的差值,同时ans+1.当ALL <= k时 ans即为结果。

最开始时我可以理解每次取的值就是最优,但是无法理解为什么这个最优的值可以取得到?因为我的想法是如果有最开始时如果有(L, 4) - (L , 3)就是最优,但是在最开始不存在(L, 3)这个状态。后面手推了几次之后感觉有上面那个不等式。在优先队列中的最大值必定是那些(L,X) 中 X更小的情况,因此不可能直接跳过当前可行分割的状态,而从队列中取出之后的分割状态。

代码:

#include<bits/stdc++.h>
#include<sstream>
#pragma GCC optimize(2)
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 2e7+10;
const int MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

LL calc(LL len, LL x)
{
    len -= (x-1);
    LL a = len / x;
    LL b = len %x;
   LL res=  (x - b) * (a) * (a+1) / 2 + (b) * (a+1) * (a+2) /2;
   //printf("len  = %lld, x= %lld, res = %lld \n", len, x, res);
   return res;
}

void solve()
{
    LL n , k;cin >> n >> k;
    LL ans = 0;
    LL sum = 0;
    LL len = 0;
    string arr;cin >>arr;
    vector<LL> rec;
    priority_queue<LL> full;
    for(int i=0;i<arr.size();++i)
    {
        if(arr[i]=='0')
        {
            if(len>0)
            {
                sum += len*(len+1)/2;
                rec.push_back(len);
                len = 0 ;
            }

        }
        else
        {
            len++;
        }
    }
    if(len)
    {
        rec.push_back(len);
        sum += len *(len+1) /2;
    }
    for(int i=0;i<rec.size();++i)
    for(int k=1;k<=rec[i];++k)
    {
        full.push(calc(rec[i], k) - calc(rec[i], k+1));
    }
    while(sum>k)
    {
        ans ++;
        //printf("%lld\n", full.top());
        sum -= full.top();full.pop();
    }
    printf("%lld", ans);
}

int main()
{

    ios::sync_with_stdio(false);
    solve();
    return 0 ;
}

J. JXC&amp;Jesus

题意:懒得写了

​ 只要我们迅速得到x的最小的质数因子,就可以迅速计算出f(x,m)。因此问题就在于如何快速得到最小的质数因子。线性筛法正是通过枚举合数的最小质因子来筛取素数。先预处理\(10^7\)个数得到它们的最小质因子即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 2e7+10;
const int MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

LL pre[maxn];

void init()
{
   for(int i=2;i<=maxn-5;++i)
   {
       if(pre[i]) continue;
       for(int k=i;k<=maxn-5;k+=i)
       {
           if(pre[k]) continue;
           pre[k] = i;
       }
   }
   return;
}

int main()
{

    ios::sync_with_stdio(false);
    LL n , m , L;
    cin >>  n >>m >> L;
    init();
    LL ans = 0;
    if(m==1)
    {
        printf("0");
        return 0;
    }
    for(int i=L+1;i<=L+n;++i)
    {
        LL di = pre[i];
        LL tem = i;
        LL cnt = 0;
        while(tem%di==0)
        {
            cnt++;tem/=di;
        }
        tem = i;
        cnt = cnt  - cnt/m;
        while(cnt--) tem/=di;
        ans += i-tem;

    }
    cout << ans;
}

K. Kazusa&rsquo;s Party

题意:有男女生各n个人,每个男生有一个整数a,女生有一个整数b。当一对男女gcd(a , b)> 1,男女可以配对。问最多有几个配对。

​ n <= 8直接暴力枚举。

代码

#include<bits/stdc++.h>
#include<sstream>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 1e5+10;
const int MOD = 998244353;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

LL arr[maxn];
int boy[19];
int girl[10];
    int p[19];
    int n;
int gcd(int a, int b)
{
    while(a^=b^=a^=b%=a);
        return b;
}

int calc()
{
    int rec=  0;
    for(int i=1;i<=n;++i)
    {
        if(gcd(boy[i], girl[p[i]])>1) rec++;

    }
    return rec;

}

void solve()
{
    memset(boy, 0 , sizeof boy);
    memset(girl , 0  ,sizeof girl);

    for(int i=1;i<=8;++i)
        p[i] = i;
    cin >> n;
    for(int i=1;i<=n;++i)
    {
        cin >> boy[i];
    }
    for(int i=1;i<=n;++i)
    {
        cin >> girl[i];
    }
    int ans = 0;

    do
    {
        ans = max(ans, calc());

    }while(next_permutation(p+1, p+1+n));
    cout << ans;
}

int main()
{
    solve();
}

L. Little Witch Academia

题意:你有宽度为a高度为1和宽度为b高度为1的砖很多个。两种砖块宽度不等现在要砌一个高度为h宽度为w的墙。同时你的砖不能旋转,只能横着摆。同时为了墙的安全,上下两层不能存在位置相同的竖缝。比如

。但是最左侧与最右侧的边缘不算,因此可以由一块砖构成一层。

​ 墙最宽为20,而砖块最短为2和3.此时一层有114种摆放方法。因此最多有每层有114种情况。将每种情况用整数表示,二进制的1即为缝的位置。当两种情况A&B==0,这两种可以摆放在相邻层。

​ dfs搜索一层中所有的摆放情况,用无向图中的点表示一层的所有摆放情况,两个点之间有边表示这两种情况可以摆放在相邻层。问题便转化为了在一个无向图中从任意点出发走h-1步,共有多少种路径。

​ 用矩阵\(A_k\)[i][j],走了k步之后,从i出发在j结束有几种不同的路径。T[i][j]=1表点i, j 之间有边。有\(A_{k+1}\)= \(A_k\)* T.

\(A_0\)为单位矩阵, \(A_k\) = T * \({A_0}^k\). ans = \(\sum\)\(A_{h-1}\) .运用矩阵快速幂时间复杂度为O(\(114^3*log^{10^9}\)).

代码:

#include<bits/stdc++.h>
#include<sstream>
#pragma GCC optimize(2)
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long Uint;
typedef pair<int, int> P;
const int maxn = 122;
const LL MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

struct Matrix
{
    LL n , m;
    LL a[maxn][maxn];
    void clear()
    {
        n = m = 0;
        memset(a, 0 , sizeof a);
    }


        Matrix operator * (const Matrix & b) const
    {
        Matrix tem;
        tem.clear();
        tem.n = n; tem.m = m;
        for(int i = 0 ;i < n;++i)
        for(int j = 0; j < b.m;++j)
        for(int k=0;k<m;++k)
            tem.a[i][j]  =  (tem.a[i][j] + (a[i][k] * b.a[k][j]) % MOD ) %MOD;
        return tem;
    }
};

Matrix qpow(Matrix T , LL n )
{
    Matrix res;
    res.clear();
//    printf("T.n = %lld\n", T.n);
    res.n =  res.m = T.n;
    for(int i=0 ; i<res.n;++i)
     res.a[i][i] = 1;
     while(n)
     {
//         printf("1\n");
         if(n&1) res= res * T;
         T = T * T;
         n >>= 1;
     }
     return res;
}
   vector<LL> rec;
void dfs(LL now  ,LL gap , LL a, LL b , LL w)
{

 // printf("now = %lld, a =%lld, b  =%lld ,gap = %lld\n",now, a, b, gap);
  if(now>w) return;
  if(now==w) {  rec.push_back(gap);return;}
  if(now+a==w) dfs(now+a, gap, a, b, w);
  else dfs(now+a, gap|(1<<(now+a)), a, b, w);
    if(now+b==w) dfs(now+b, gap, a, b, w);
  else dfs(now+b, gap|(1<<(now+b)), a, b, w);


    return;
}


void solve()
{
    rec.clear();
    LL a , b ,w , h;cin >> a >> b >>w >> h;
    dfs(0, 0, a, b, w);

    int len = rec.size();
//    printf("len  = %lld\n" , len);
    Matrix res ;
    res.clear();
    res. m = res.n = len;
    for(int i=0 ; i< len ;++i)
    for(int j = 0 ; j < len ; ++j)
    {

        LL a =rec[i] , b = rec[j];
        if(a&b) continue;
        res.a[i][j] = 1;
        res.a[j][i] = 1;
//         printf(" i = %d, j = %d\n", i , j);
    }

    LL ans = 0;
    res = qpow(res , h-1);
    for(int i=0;i<res.n;++i)
    {
    for(int j =0 ;j <res.m;++j)
        {
            ans = (ans + res.a[i][j]) % MOD;
        }
    }
    printf("%lld\n" , ans);

}

int main()
{

    ios::sync_with_stdio(false);
    int T;cin >> T;
    while(T--)
        solve();
}

M. Minecraft

题意:将长宽高为(n, m ,h)的立方体中的每一个位置用A-Z中的一个字母标记。每次你可以选择一个字母,然后空间中该字母位置的会放置一个沙子,一个沙子会掉落当该沙子下方没有沙子。为是否可以选择一系列字母顺序使得该立方体被填满。如果可以输出最大的字典序字母序列,不能输出-1.

思路:遍历第一层,对于每一个方格从下到上建立有向图后拓扑排序,并同时用优先队列维护最大字典序。

代码:

#include<bits/stdc++.h>
#include<sstream>
using namespace std;
#define  mp make_pair
#define REP(i,a,b) for(int i=a;i<b;i++)
typedef  long long LL ;
typedef unsigned long long ULL;
typedef pair<int, int> P;
const int maxn = 2e3+10;
const int MOD = 1e9+7;
const int INF = 1e9+7;
const double PI = acos(-1);
const double eps = 1e-7;

string bloc[33][33];
int n, m , h;
int T;

int in[400];
int vis[400];
vector<int> vex[400];

void solve()
{
    cin >> n >>m >>h;
    //cout <<n <<m << h;
    memset(in, 0, sizeof in);
    memset(vis, 0 , sizeof vis);
    for(int i='A';i<='Z';++i)
        vex[i].clear();
    for(int i=0;i<30;++i)
    for(int k=0;k<30;++k)
     bloc[i][k].clear();
    for(int i=h-1;i>=0;--i)
    {
        for(int k=0;k<n;++k)
        {
            cin >>bloc[i][k];
        }
    }
     for(int i=0;i<n;++i)
    for(int j =0  ; j<m;++j)
    {
        int now = bloc[0][i][j];
        vis[now] = 1;
    }
    for(int i=0;i<n;++i)
    for(int j =0  ; j<m;++j)
    for(int k=0;k<h-1;k++)
    {
        int now = bloc[k][i][j];
        int Next=  bloc[k+1][i][j];
        vis[now] = 1;
        vis[Next] = 1;
        if(now!=Next)
        {
           vex[now].push_back(Next);
           in[Next]++;
        }

    }
    priority_queue<int> rec;
    for(int i='A';i<='Z';++i)
        if(!in[i]) {rec.push(i);}
    vector<int> ans;
    int cnt = 0;
    while(!rec.empty())
    {
        cnt++;
        int  u = rec.top();
        rec.pop();
        ans.push_back(u);
        for(int i=0;i<vex[u].size();++i)
        {
            int v  = vex[u][i];
            in[v]--;
            if(in[v]==0)
            {
                rec.push(v);
            }
        }
    }
    if(cnt< 26)
    {
        printf("-1");
    }
    else
    {
        for(auto c:ans)
            if(vis[c])printf("%c", c);
    }
    printf("\n");
    return;


}

int main()
{

    ios::sync_with_stdio(false);
     cin >> T;
     while(T--)
    solve();

}

posted on 2021-06-15 23:52  安乐最值钱  阅读(211)  评论(0)    收藏  举报

导航