理解[bzoj 3243][Noi2013]向量內積

傳送門
題目大意:給定n個d維向量,輸出任意一對向量,滿足他倆的內積爲k的倍數。
我想了一個裸的隨機算法:隨機選兩個向量求內積,然後。。。後10個點只過了兩個。。。看到標準解法也是用的隨機,一開始感覺非常不服他的隨機到底比我的強在哪。。。(後來發現是我太弱)
值得一提的是,此題必須使用隨機算法來確定答案。只取全1向量的做法是錯誤的。
一組很簡單的數據就能卡掉(不信?試一試)

meow.in
4 6 2
1 1 0 0 0 0
1 1 1 1 0 0
1 1 1 0 0 1
1 0 0 1 1 1
meow.out(樣例)
1 2 

題解吧……先放在後面

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
#define rep(I, S, T) for (int I = S; I <= T; I ++)
#define rst(ARR) memset(ARR, 0, sizeof(ARR))
int read(){
    int ret = 0; char ch;
    do ch = getchar(); while (ch<'0' || ch>'9');
    do ret = ret*10+ch-'0', ch = getchar(); while (ch>='0' && ch<='9');
    return ret;
}
const int MAXN = 100005;
const int MAXK = 105;
int n, d, k;
int seq[MAXN][MAXK], diag[MAXN], X[MAXN], res[MAXN], Y[MAXN];
void check(int i, int j) {
    int sum = 0;
    rep(p, 1, d) sum += seq[i][p]*seq[j][p];
    sum %=k;
    if (sum==0 ) {
        if (i>j) swap(i, j);
        printf("%d %d\n", i, j);
        exit(0);
    }
}
void solve2() {
    rep(T, 1, 10) {
        int s = 0;
        rst(res);rst(Y);
        rep(i, 1, n) X[i] = rand()%k, s+=X[i];
        s%=k;
        rep(i, 1, n) rep(j, 1, d) res[j] += seq[i][j]*X[i];
        rep(i, 1, d) res[i]%=k;
        rep(i, 1, n) {
            rep(j, 1, d) Y[i] += res[j]*seq[i][j];
            Y[i] += k-diag[i]*X[i];
            Y[i]%=k;
        }
        rep(i, 1, n) if ((Y[i] + X[i])%k!=s) 
            rep(j, 1, n) if (j!=i)check(i, j);
    }
}
void solve3() {
    rep(i, 1, n) diag[i]=(bool)diag[i];
    rep(T, 1, 7) {
        int s = 0;
        rep(i, 1, d*d) res[i] = 0;
        rep(i, 1, n) X[i] = rand()%k, s+=X[i];
        s%=k; 
        rep(i, 1, n) {
            int *s = seq[i], pt = 0;
            rep(j, 1, d) rep(p, 1, d) res[++pt] += s[j]*s[p]*X[i];
        }
        rep(i, 1, d*d) res[i]%=k;
        rep(i, 1, n) {
            int *sq = seq[i], pt = 0;
            Y[i] = 0;
            rep(j, 1, d) rep(p, 1, d) Y[i] += res[++pt]*sq[j]*sq[p];
            Y[i] += k-diag[i]*X[i]; Y[i]%=k;
            if ((Y[i] + X[i])%k!=s) 
                rep(j, 1, n) if (j!=i) check(i, j);
        }
    }
}
int main()
{
    srand(223333333);
    scanf("%d%d%d", &n, &d, &k);
    rep(i, 1, n) rep(j, 1, d) seq[i][j] = read()%k;
    rep(i, 1, n) {
        rep(j, 1, d) diag[i] += seq[i][j]*seq[i][j];
        diag[i]%=k;
    }
    if (k==2) solve2(); else solve3();
    puts("-1 -1");
    return 0;
}

首先!模型轉換:nd 維向量構成了一個nd 的矩陣A ,現在計算AAT ,得到的n 階方陣B
Bi,j 表示向量i與向量j的內積
好啊!非常優美,但是直接計算複雜度仍然是O(n2d)
暫時只考慮k=2
我們更關心是否存在0元素對吧
可以拿B 和全一方陣C 比較一下,若有不同,說明存在0元素
爲了降低複雜度,我們不能對nn 矩陣進行一一計算,所以不妨取一個1n 的隨機向量X
根據結合律,計算XAATXC ,比較結果。而向量乘矩陣的複雜度是O(nd) 的,比較資磁
(似乎這是判斷矩陣是否相等的經典辦法?)
只要有一個元素不同=>對應列上存在一個0向量,只需要暴力枚舉尋找位置
沒有元素不同=>X選得不好或者本來就沒有
根據定理**,正確概率至少有1/2 ,那麼重複十餘次就可以得到結果了
(2014年胡澤聰的集訓隊論文可以參考一下)

然後問題來了:
Q1.B 中 對角線上可能存在0丫(aiai=0
A1:這個比較重要,要將干擾排除。幸運的是,只有n個內積需要計算,這樣計算XAAT 時候需要把這n個內積的結果減去(消除影響),然後在對角線上加一進行比較。複雜度還是O(nd)

Q2.爲什麼要隨機。。直接取全一向量不行麼?
A2:D=XAAT ,這裏解釋一下D 代表的意義:
AAT 得到的n 階方陣B 中,把第j 列拿出來,把B 中每一行的元素與X 中元素對應相乘後相加,得到的結果就是Dj 。如果X 取全一向量,那麼Dj 就是這一列上所有元素的和。如果不全是一,那麼Dj 是其中一個子集的和。
看起來取全一向量非常靠譜,可是不要忘記——我們是在mod2 意義下操作。如果這一列1的取值個數正好是2的倍數,那麼這一個位置Dj=0 。如果所有位置都是0,會被認爲是無解,儘管可能有不止一對向量的內積是0。
理想的情況當然應該是先把B 對2取模,然後對於轉換後的矩陣進行整數系中的運算。但是……這樣就不能應用乘法分配律了……
網上有一部分沒有使用隨機化的做法,隨便找出一個,就能用開頭的辦法卡掉。
所以靠譜的做法是:在k=2 下隨機,計算結果第i 列的值爲從B 矩陣中找出第i 列一個隨機子集的點積之和,還是有挺大的概率得到一個非0數字的

Q3.同樣是隨機,爲什麼裸隨機不如套用矩陣進行隨機?
A3:別忘了,在標準做法中,每次可以對一整列進行判定,只要有一個出錯,整個就會出錯,而n列的判定問題又可以在O(nd) 時間內解決(隨機化)。相當於是把多個內積打包在一起進行判定。

Q4.光顧着說k=2 去了,k=3 該怎麼做?和k=2 有什麼區別?
A4:這一次,矩陣B中不全是1了(有可能是2),不能把它和全一矩陣比較了。
怎麼辦呢?把每一個內積的值平方!因爲21(mod3) ,平方以後還是1,就轉化成了全1矩陣
把內積平方拆開:

(i=1daibi)(j=1dajbj)=i=1dj=1daiajbibj

可以看成一個d^2維向量c⃗  ,其中
c(i1)d+j=aiaj,1i,jn

ba 同理
這樣就完成了問題轉化,只是需要用O(d2) 時間計算了

BTW
當初上uoj想找一份標程扒一下(因爲太弱了,並不會),發現好多人用了一樣的代碼汗,而且真正用隨機向量的人不多啊。
構造了反例,想Hack一下,可惜那道題是一道spj,沒法Hack
於是出於業(xian)界(zhe)良(mei)心(shi),我想vfk大大發了郵件,希望加強一下數據
結果第二天發現vfk凌晨回覆了郵件!並且添加了Extra Test!這種敬業精神必須要贊!!
我還想加強一下BZOJ數據。。不過TA1111爺似乎也注意到了。。比我早幾天。。那麼我就不把事情做絕了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章