洛谷P2831 NOIP2016 愤怒的小鸟
题目
思路
个人感觉我的思路并不是很优
m貌似没什么用(可能是部分分),直接忽略
但是看到n最大才去到18,不是状压就是爆搜,我采用的是DFS维护状压DP的转移(其实有点像记忆化搜索)
注意:为了方便状压,以下小猪的编号从0~n-1
设状态k二进制下第i为表示:第i只小猪的消灭情况0:没消灭,1:消灭,f[k]表示状态为k时最少用的小鸟数量
状态转移:
\[f_k=\min(f_{s}+1)
\]
其中s满足(s | k == k)且对于所有i,i满足((k >> i)&1==1 ,(s>>i)&1==0),编号为i的小猪在同一条合法的抛物线上(即可以用同一只小鸟解决)
特别地,当k的二进制下只有一位为"1"时,f[k]=1,k==0时,f[k]=0.
所以对于每个状态k,我们需要枚举两只小猪i,j,再去掉和小猪i,j在同一条合法抛物线上的小猪,时间复杂度O(n3×2n)是会超时的
优化:
-
预处理小猪两两组合产生的抛物线:常数级优化
-
考虑到在同一条抛物线上的小猪两两组合产生的抛物线是一样的,不必重复枚举,所有对于每一次dfs转移,定义vis[i] [j]表示是有枚举过小猪i,j,若找到小猪k,k与i,j在同一条抛物线上
vis[i][k] = vis[k][i] = vis[j][k] = vis[k][j] = true
玄学级优化
- 题解上有说预处理所有和i,j在同一条抛物线上的小猪,但是我没写出来,题解传送门,优化为O(n2×2n)
然后发现还是有两个TLE我一气之下开了O2把这题切了
一些小问题
精度:
亲测1e-4是不够的,最后我开的是1e-8
抛物线解析式:
具体查看初二的数学课本,不做赘述
inline void cal(int i , int j , double &a , double &b) {
if(x[i] == 0 || x[j] == 0 || x[i] == x[j]) {
a = 999;//不合法
return;
}
a = (y[i] * x[j] - y[j] * x[i])/(x[i] * x[j] * (x[i] - x[j]));
b = y[i] / x[i] - a * x[i];
}
tips:
代码
我的(1.cpp)
#include <iostream>
#include <cstdio>
#include <cstring>
#define rr register
//#pragma GCC optimize(2)
using namespace std;
int read() {
int re = 0 , sig = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-')sig = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
re = (re << 1) + (re << 3) + c - '0';
c = getchar();
}
return re * sig;
}
int n;
double x[20] , y[20];
double aa[20][20] , bb[20][20];
inline void cal(int i , int j , double &a , double &b) {
if(x[i] == 0 || x[j] == 0 || x[i] == x[j]) {
a = 9;
return;
}
a = (y[i] * x[j] - y[j] * x[i])/(x[i] * x[j] * (x[i] - x[j]));
b = y[i] / x[i] - a * x[i];
}
inline double fabss(double a) {
return a < 0 ? -a : a;
}
inline bool check(double a , double b , int i) {
return fabss(a * x[i] * x[i] + b * x[i] - y[i]) < 1e-8;
}
inline int count_(int x){
int cnt = 0;
while(x != 0){
x = x & (x - 1);
cnt++;
}
return cnt;
}
void print(int x) {
if(x >= 2)
print(x / 2);
putchar(x % 2 + 48);
}
int f[1 << 20];
int dfs(rr int stat) {
if(f[stat] != -1)
return f[stat];
double a , b;
int count_res = count_(stat);
if(count_res == 0)
return 0;
if(count_res == 1)
return 1;
if(count_res == 2){
int i = 0 , j = 0 , tmp = stat;
while(1) {
if(tmp & 1)
break;
tmp >>= 1 , ++i , ++j;
}
tmp >>= 1;
++j;
while(1) {
if(tmp & 1)
break;
tmp >>= 1 , ++j;
}
a = aa[i][j];
b = bb[i][j];
// cout << i << '\t' << j << endl;
f[stat] = (a >= 0 ? 2 : 1);
return f[stat];
}
bool vis[20][20];
memset(vis , 0 , sizeof(vis));
f[stat] = count_res;
rr int tmp , temp;
for(rr int i = 0 ; i < n ; ++i)
if((stat >> i) & 1)
for(rr int j = 0 ; j < n ; ++j) {
if(vis[i][j])continue;
vis[i][j] = vis[j][i] = true;
if(((stat >> j) & 1) == 0) continue;
if(i == j)continue;
a = aa[i][j];
b = bb[i][j];
if(a >= 0)continue;
tmp = stat;
tmp ^= (1 << i);
tmp ^= (1 << j);
for(rr int k = 0 ; k < n ; ++k)
if(((tmp >> k) & 1 ) == 1 && check(a , b , k)) {
tmp ^= (1 << k);
vis[i][k] = vis[k][i] = vis[j][k] = vis[k][j] = true;
}
temp = (f[tmp] == -1 ? dfs(tmp) + 1 : f[tmp] + 1);
if(temp < f[stat]) {
f[stat] = temp;
}
}
return f[stat];
}
int main() {
// freopen("angrybirds13.in" , "r" , stdin);
int T = read();
while(T--) {
memset(f , -1 , sizeof(f));
n = read();
read();
for(int i = 0 ; i < n ; i++)
cin >> x[i] >> y[i];
for(int i = 0 ; i < n ; ++i)
for(int j = 0 ; j < n ; ++j)
if(i != j)
cal(i , j , aa[i][j] , bb[i][j]);
cout << dfs((1 << (n)) - 1) << endl;
}
return 0;
}
自测程序(compare.cpp)
请将下载的数据,待测程序,自测程序放在同一文件夹下
#include <bits/stdc++.h>
using namespace std;
string itos(int x) {
string s;
while(x != 0){
s = (char)(x % 10 + 48) + s;
x /= 10;
}
return s;
}
int main() {
for(int i = 1 ; i <= 20 ; i++) {
printf("No.%d\n" , i);
int t = clock();
system(("1.exe < " + (string)"angrybirds" + itos(i) + ".in " + " > output.txt").c_str());
printf("%dms\n" , clock() - t);
if(system(("fc output.txt " + (string)"angrybirds" + itos(i) + ".ans").c_str())){
cout << "WA!\n";
system("pause");
}//*/
}
return 0;
}