A. Odd Divisor
題目大意:給定一個\(n\)問是否存在大於\(1\)的奇因子.
思路
除\(2\)直到最后檢查即可.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
int T;scanf("%d",&T);
while(T--)
{
ll n;scanf("%lld",&n);
while(n % 2 == 0) n /= 2;
if(n > 1) puts("YES");
else puts("NO");
}
return 0;
}
B. New Year's Number
題目大意:給定一個數問是否能由若干個\(2020\)和若干個\(2021\)的和表示出來.
思路
一開始還以為是賽瓦韋斯特定理,形式就是問\(n\)是否能表示成\(2020x+2021y\)且\(x,y \geq 1\).就跟賽瓦韋斯特定理沒啥關系了,可以直接求出一個\(x = n / 2020\)表示\(n\)中含有多少個\(2020\),還求一個\(n\)對\(2020\)的余數,那么現在的變換就可以看做是把某些\(2020\)變成\(2021\),一共有余數個\(2021\)判斷是否足夠變換即可.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
int c = n / 2020,d = n % 2020;
if(c >= d) puts("YES");
else puts("NO");
}
return 0;
}
C. Ball in Berland
題目大意:有\(n\)個男的\(m\)個女的,還有\(k\)對關系\((a,b)\)表示第\(a\)個男的可以和第\(b\)個女的跳舞.現在要求你從\(k\)對關系里選出兩對不干擾的男女,不干擾即是指同一個人不能出現在兩對關系里,問選出兩對關系的方案數有多少個.
思路
選出兩對,直接枚舉一條邊\((u,v)\)作為選取的一對人,那么現在的問題就是在所有\(k\)個關系之中,有多少個關系是和\((u,v)\)無關的,也就是在\(k\)跳邊之中有多少條邊連接的兩個點既不是\(u\)也不是\(v\).這個顯然可以求一個補集,也就是求與這兩個點有關的邊有多少個,再從\(k\)個里面去掉就可以了.那么顯然也就是兩個點的度數之和減一,因為\((u,v)\)這條邊會被計入兩次.那么枚舉答案就可以了.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7,M = 2 * N;
int a[N],b[N];
int deg[M];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,m,k;scanf("%d%d%d",&n,&m,&k);
forn(i,1,n + m) deg[i] = 0;
forn(i,1,k) scanf("%d",&a[i]),++deg[a[i]];
forn(i,1,k) scanf("%d",&b[i]),++deg[b[i] + n];
ll res = 0;
forn(i,1,k)
{
// cout << deg[a[i]] << " " << deg[b[i] + n] << endl;
int sum = deg[a[i]] + deg[b[i] + n] - 1;
res += k - sum;
}
printf("%lld\n",res / 2);
}
return 0;
}
D. Cleaning the Phone
題目大意:有\(n\)個垃圾,每個垃圾體積是\(a_i\),同時每個垃圾根據自己的處理難度需要花費\(b_i\)元.問至少丟掉\(m\)體積的垃圾,最少需要花多少錢.
在本題中,\(b_i\)僅有兩種取值\(1/2\).
思路
因為\(b_i\)只有兩種取值,所以第一步可以先分組,把所有花費為\(1\)的放在一起,花費\(2\)的同理.那么一個比較顯然的方向就是直接把兩個數組都排序,排序完了之后貪心的選擇兩邊的策略.不過很顯然貪心是無法處理很多特殊情況的,需要不停地打補丁,不考慮直接貪心.
另外一個想法就是直接枚舉,當然不能枚舉兩邊選多少,只能枚舉一邊,簡單起見枚舉花費\(2\)的垃圾選了\(i\)件,選擇是從體積大的往體積小的枚舉,那么可以維護一個當前花費\(2\)的垃圾總共有多少體積,剩下在\(1\)的垃圾中至少要選出\(m-cur\)體積的垃圾,這一步可以先給花費\(1\)的數組從大到小排序,排序之后直接二分找到第一個位置\(\geq m - cur\)的位置,那么這個位置就是需要選出的個數,直接更新答案就可以了.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7;
int a[N],b[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,m;scanf("%d%d",&n,&m);
forn(i,1,n) scanf("%d",&a[i]);
forn(i,1,n) scanf("%d",&b[i]);
vector<int> a1,a2;
forn(i,1,n) if(b[i] == 1) a1.push_back(a[i]);else a2.push_back(a[i]);
sort(a1.begin(),a1.end());reverse(a1.begin(),a1.end());
sort(a2.begin(),a2.end());reverse(a2.begin(),a2.end());
vector<ll> sum;sum.push_back(0);for(auto& v : a1) sum.push_back(sum.back() + v);
ll cur = 0;int res = 1e9;
{
auto iter = lower_bound(sum.begin(),sum.end(),m - cur);
if(iter != sum.end()) res = min(res,(int)(iter - sum.begin()));
}
for(int i = 0;i < a2.size();++i)
{
cur += a2[i];
auto iter = lower_bound(sum.begin(),sum.end(),m - cur);
if(iter == sum.end()) continue;
res = min(res,(i + 1) * 2 + (int)(iter - sum.begin()));
}
printf("%d\n",res == 1e9 ? -1 : res);
}
return 0;
}
E. Advertising Agency
題目大意:現在要賣一個商品,有\(n\)個吹牛逼的人,每個吹牛逼的人有\(a_i\)個粉絲,假設每個人的粉絲集合都沒有重合的地方.現在想要雇佣\(k\)個人上微博吹牛逼,問在選出的\(k\)個人的粉絲總和數量最大的前提之下,有多少種選擇方案.
思路
傻逼題.
值得選擇的部分只有最后一個人,舉個例子來說就是\(1,1,2,3,4\)如果要選出\(4\)個人,那么首先排序,要讓和最大肯定從大的往小的選,那么除了\(1\)這個最后一個數之外的數都沒有任何選擇的余地,只有最后一個數可能會有選擇的余地,只需要找出整個序列中有多少個\(1\)以及被\(k\)個數選入的有多少個,那么答案就是在整個序列中有多少個中選出被選入的個數,組合數即可.
由於數據范圍小的離譜,,,這題直接用楊輝三角遞推組合數即可,不用乘法逆元.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 1005,MOD = 1e9+7;
int a[N],C[N][N];
void init()
{
for(int i = 0;i < N;++i)
{
for(int j = 0;j <= i;++j)
{
if(!j) C[i][j] = 1;
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
}
int main()
{
init();
int T;scanf("%d",&T);
while(T--)
{
int n,k;scanf("%d%d",&n,&k);
forn(i,1,n) scanf("%d",&a[i]);
sort(a + 1,a + n + 1);
int cnt = 0,sz = 0,target = a[n - k + 1];
forn(i,1,n)
{
if(a[i] == target)
{
++cnt;
if(i >= n - k + 1) ++sz;
}
}
// cout << cnt << " " << sz << endl;
printf("%d\n",C[cnt][sz]);
}
return 0;
}
F. Unusual Matrix
題目大意:給定一個\(n*n\)的矩陣,元素只有\(0/1\).每次可以選出矩陣中的一行或者一列全部異或1.問若干次操作之后矩陣\(a\)是否能變成\(b\).
思路
老套路題了.
首先注意到所有的操作,第一個如果對同一行或者同一列操作兩次是毫無意義的,第二個操作的先后執行順序也是毫無意義的.所以我們不妨把無序的操作方案變成有規律的:先把所有的方案分成兩種:一種是執行了第一行反轉的,另外一種是沒有執行第一行反轉的.
如果把所有方案如上分成兩種,那么接下來可以注意到一個結論:第一行的某個元素如果與\(b\)中某個元素不對應,那么他一定需要反轉,但是把所有方案如上分開的意義就在於,接下來不能使用反轉第一行的操作了,也就是說現在如果跟目標矩陣某個元素不相同,只能通過反轉一個列的操作使之相等.在做完了第一行之后,同樣根據一開始的結論,接下來任何列,如果再操作一次,就是毫無意義的,進而現在如果有某一行元素不相同,那么只能動用行操作,繼續往下推,如果發現某行第一個元素不同就反轉這一行.到最后檢查整個矩陣是否是和目標矩陣相同的就就可以了.
當然由於方案分割成了兩個大的集合,先做一次再把第一行完全反轉再做一次就行了.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 1005;
char sa[N][N],sb[N][N];
int a[N][N],b[N][N];
int n;
bool check(vector<vector<int>> a,vector<vector<int>> b)
{
forn(j,1,n)
{
if(a[1][j] != b[1][j])
forn(i,1,n) a[i][j] ^= 1;
}
forn(i,1,n)
{
if(a[i][1] != b[i][1])
forn(j,1,n) a[i][j] ^= 1;
}
forn(i,1,n) forn(j,1,n) if(a[i][j] != b[i][j]) return 0;
return 1;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
forn(i,1,n) scanf("%s",sa[i] + 1);
forn(i,1,n) scanf("%s",sb[i] + 1);
forn(i,1,n) forn(j,1,n) a[i][j] = (sa[i][j] - '0');
forn(i,1,n) forn(j,1,n) b[i][j] = (sb[i][j] - '0');
vector<vector<int>> a_(n + 17,vector<int>(n + 17)),b_(n + 17,vector<int>(n + 17));
forn(i,1,n) forn(j,1,n) a_[i][j] = a[i][j];
forn(i,1,n) forn(j,1,n) b_[i][j] = b[i][j];
if(check(a_,b_))
{
puts("YES");
continue;
}
forn(i,1,n) a_[1][i] ^= 1;
if(check(a_,b_)) puts("YES");
else puts("NO");
}
return 0;
}
G. Strange Beauty
題目大意:定義一個序列\(a\)是牛逼的,當且僅當對於任意一對\(i,j\)在\(i\neq j\)的前提下如果有\(a_i | a_j\)或者\(a_j | a_i\).現在給定一個序列\(a\),問最少刪掉幾個數可以使整個序列變成牛逼的.
思路
還是一樣的,先把所有數排個序,這樣的話牛逼的序列條件就可以等價的變成所有較小的數都是較大的數的因子,這樣的話可以只考慮一邊的關系,本來的序列要考慮大的到小的小的到大的不好做.
剩下的可以直接\(dp\),考慮\(f[i]\)表示\(i\)作為整個序列里面最大的數的時候,這個序列最多包含多少個數,轉移就是\(f[i] = cnt(i) + \max\{f[y]\}\)式中\(y\)是\(i\)的因子,且\(y < i\).\(cnt(i)\)表示原來的序列里面\(i\)的個數.這個\(dp\)的依據就是划分最后一個數.
由於這個題時限放到了5s
.所以可以暴力做這個題:直接對於每個數根號分解他的因數.當然還有一個\(logn\)調和級數的做法,也就是枚舉每個數\(x\)有哪些數\(y\)是包含\(x\)作為因子的,直接把\(x\)的貢獻算進去就可以了.太簡單了也沒什么好說的.
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7;
int a[N],cnt[N],f[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
int maxv = 0;
forn(i,1,n) scanf("%d",&a[i]),maxv = max(maxv,a[i]);
forn(i,1,maxv) cnt[i] = 0,f[i] = 0;
forn(i,1,n) ++cnt[a[i]];
forn(x,1,maxv)
{
if(!cnt[x]) continue;
f[x] += cnt[x];
for(int y = 2 * x;y <= maxv;y += x)
f[y] = max(f[y],f[x]);
}
int res = 0;
forn(i,1,maxv) res = max(res,f[i]);
printf("%d\n",n - res);
}
return 0;
}