1. 程式人生 > >【8.12校內測試】【容斥原理】【數位DP(二進位制)】【壓維(?)】

【8.12校內測試】【容斥原理】【數位DP(二進位制)】【壓維(?)】

emm,今天測試沒有什麼感想。。

1 a

1.1 問題描述

有n 個青蛙,m 個石頭圍成一圈編號為0 m��1,第i 只青蛙每次跳ai 步,這意味著青
蛙能從石頭j mod m 跳到石頭(j + ai) mod m。青蛙每跳一個石頭,就佔領它。每隻青蛙最開
始在0 號石頭,它們可以一直跳下去。這些青蛙最後佔領的石頭編號和為多少?

1.2 輸入

第一行一個整數T,接下來T 組資料,每組資料輸入兩行。
第一行輸入兩個整數n;m。第二行輸入n 個整數ai。

1.3 輸出

對於每組資料輸出一行,形如”Case[空格][井號]X:[空格]Y”。X 為資料編號,從1 開始,Y
為佔領的石頭編號和。(排版有問題)

1.4 輸入輸出樣例1

1.4.1 輸入樣例

3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72

1.4.2 輸出樣例

Case ♯1: 42
Case ♯2: 1170
Case ♯3: 1872

1.5 約定和資料範圍

對於30% 的資料, n 10, m 1e3,
對於100% 的資料, 1 T 50, 1 n 1e4, 1 m 1e9, 1 ai 1e9。

把所有的跳躍長度處理出來(就是gcd(m,a[i]),去算貢獻的時候容斥,記錄當前出現的次數和應該出現的次數
#include<iostream>
#include<cstdio> #include<cstring> #include<vector> #include<algorithm> #define ll long long using namespace std; ll m, a[10005]; int n, flag[10005], ti; int G[10005], tot, sum, appear[10005], cnt[10005], qwq[10005]; int gcd ( int a, int b ) { return b == 0 ? a : gcd ( b, a % b ); } void init ( int
x ) { memset ( qwq, 0, sizeof ( qwq ) ); memset ( appear, 0, sizeof ( appear ) ); for ( int i = 1; i * i <= x; i ++ ) { if ( x % i == 0 ) { qwq[++sum] = i; if ( i * i != x ) qwq[++sum] = x/i; } } if ( x != 1 ) qwq[++sum] = x;////m分解因數,表示可能做出貢獻的跳躍長度,m不算 } int main ( ) { freopen ( "a.in", "r", stdin ); freopen ( "a.out", "w", stdout ); int T; scanf ( "%d", &T ); while ( T -- ) { memset ( cnt, 0, sizeof ( cnt ) ); memset ( flag, 0, sizeof ( flag ) ); memset ( G, 0, sizeof ( G ) ); sum = 0; scanf ( "%d%I64d", &n, &m ); for ( int i = 1; i <= n; i ++ ) scanf ( "%I64d", &a[i] ); init ( m ); sort ( qwq + 1, qwq + 1 + sum ); sum --; for ( int i = 1; i <= n; i ++ ) { int d = gcd ( m, a[i] ); for ( int j = 1; j <= sum; j ++ ) if ( qwq[j] % d == 0 ) appear[j] = 1;//應該出現的次數 } ll ans = 0; for ( int i = 1; i <= sum; i ++ ) { if ( cnt[i] != appear[i] ) {//cnt是當前已經出現的次數 ll tmp = ( m - 1 ) / qwq[i]; ans += tmp * ( tmp + 1 ) / 2 * qwq[i] * ( appear[i] - cnt[i] );//多了減少了加 for ( int j = i + 1; j <= sum; j ++ ) if ( qwq[j] % qwq[i] == 0 ) cnt[j] += appear[i] - cnt[i];//更新已經出現的次數 } } printf ( "Case #%d: %I64d\n", ++ti, ans ); } return 0; }

2 b

2.1 問題描述

對於數字n, [1; n] 內,有多少個數可以被由它轉化成的二進位制裡面的1 的個數整除
比如9 的二進位制形式為1001,但是9%2! = 0,所以它不是
比如8 的二進位制形式為1000,8%1 == 0,所以它是
如對於9 來說[1; 9] 內有5 個數字是合法的

2.2 輸入

輸入一個整數n

2.3 輸出

輸出一個整數結果

2.4 輸入輸出樣例1

2.4.1 輸入樣例

153

2.4.2 輸出樣例

42

2.5 約定和資料範圍

對於20% 的資料, n 50000000,
對於100% 的資料, 1 n 1e19。

將所有都統一成二進位制數,做數位dp即可。 【注意】1e19需要用unsigned long long,此時不能memset成-1,記錄vis陣列表示當前狀態是否在當前模數中被計算過(變化過的記憶化
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll unsigned long long
using namespace std;

ll n;
ll dp[70][70][70][2], fig[20], vis[70][70][70][2];

ll dfs ( int dep, int up, int mod, int num, ll sum, int pre_num ) {
    if ( vis[dep][num][mod][up] == pre_num ) return dp[dep][num][mod][up];
    if ( !dep && mod == 0 && num == pre_num ) return 1;
    if ( !dep ) return 0;
    int cnt = up ? fig[dep] : 1;
    ll ans = 0;
    for ( int i = 0; i <= cnt; i ++ ) {
        ans += dfs ( dep - 1, up && i == cnt, ( sum * 2 % pre_num + i ) % pre_num, num + i, sum * 2 + i, pre_num );
    }
    vis[dep][num][mod][up] = pre_num;
    dp[dep][num][mod][up] = ans;
    return ans;
}

ll work ( ll n ) {
    int ee = 0;
    while ( n ) {
        fig[++ee] = n % 2;
        n /= 2;
    }
    ll ans = 0;
    for ( int i = 1; i <= ee; i ++ ) {//列舉1的個數
        ans += dfs ( ee, 1, 0, 0, 0, i );
    }
    return ans;
}

int main ( ) {
    freopen ( "b.in", "r", stdin );
    freopen ( "b.out", "w", stdout );
    scanf ( "%I64d", &n );
    ll ans = work ( n );
    printf ( "%I64d", ans );
    return 0;
}

3 c

3.1 問題描述

有一個n m 的矩陣,左上角為(1; 1) 右下角為(n;m),每個格子邊長為4,每個格子中
央有權值為aij。對於一個格子的頂點,這個點的權值是
i=1nj=1mdis2aij, dis 是這個點到
格子(i; j) 中心的歐幾里得距離。求該權值最小的頂點。如有相同權值的頂點,首先選擇x 坐
標較小的頂點,如果還有相同,再選擇y 座標較小的頂點。
這裡寫圖片描述

3.2 輸入

第一行為兩個整數n;m,
接下來n 行,每行m 個整數,按圖示從左到右,從上到下,表示aij。

3.3 輸出

輸出兩行,第一行一個整數為最優點的最小權值和為多少,第二行兩個整數分別表示最優
點的x 和y 座標。

3.4 輸入輸出樣例1

3.4.1 輸入樣例

2 3
3 4 5
3 9 1
3

3.4.2 輸出樣例

392
1 1

3.5 輸入輸出樣例2
3.5.1 輸入樣例

3 4
1 0 0 0
0 0 3 0
0 0 5 5

3.5.2 輸出樣例

240
2 3

3.6 約定和資料範圍

對於20% 的資料, n or m = 1,
對於100% 的資料, 1 n;m 1000, 1 aij 100000。

可以發現,可以把貢獻拆成行和列分開計算,同一列上所有點在行上受到的其他各列的總貢獻是一樣的,行同理,最後每個點的總價值把行和列加起來即可。【注意】點和格子的座標
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;

int n, m;

ll a[1005][1005], line[1005], row[1005], row_sum[1005];
ll line_sum[1005];

int main ( ) {
    freopen ( "c.in", "r", stdin );
    freopen ( "c.out", "w", stdout );
    scanf ( "%d%d", &n, &m );
    for ( int i = 1; i <= n; i ++ )
        for ( int j = 1; j <= m; j ++ ) {
            scanf ( "%I64d", &a[i][j] );
            line_sum[j] += a[i][j];
            row_sum[i] += a[i][j];
        }
    for ( int i = 0; i <= m; i ++ ) 
        for ( int j = 1; j <= m; j ++ ) {
            if ( j <= i ) {
                line[i] += line_sum[j] * ( ( i - j ) * 4 + 2 ) * ( ( i - j ) * 4 + 2 );
            } else {
                line[i] += line_sum[j] * ( ( j - i - 1 ) * 4 + 2 ) * ( ( j - i - 1 ) * 4 + 2 );
            }
        }
    for ( int i = 0; i <= n; i ++ ) 
        for ( int j = 1; j <= n; j ++ ) {
            if ( j <= i ) {
                row[i] += row_sum[j] * ( ( i - j ) * 4 + 2 ) * ( ( i - j ) * 4 + 2 );
            } else {
                row[i] += row_sum[j] * ( ( j - i - 1 ) * 4 + 2 ) * ( ( j - i - 1 ) * 4 + 2 );
            }
        }
    ll ans = 1e18;
    int x, y;
    for ( int i = 0; i <= n; i ++ )
        for ( int j = 0; j <= m; j ++ ) {
            ll tmp = line[j] + row[i];
            if ( ans > tmp ) {
                x = i; y = j;
                ans = tmp;
            }
        }
    printf ( "%I64d\n%d %d", ans, x, y );
    return 0;
}