NC16857 [NOI1999]生日蛋糕
题目链接
关键点:
1、如何剪枝:
n为总体积,m为总层数
(1)先考虑每一层的r和h的上下界
r:因为下层的圆柱体会大于上层圆柱体,因此当前层的r至少要为当前层数,此为下界
上界:设当前已有的体积为v,那么pi*(n-v)>=pi*r*r*h;
得r<=(int)sqrt((n-v)/h) 转换成 r<=(int)sqrt(n-v);
r还有一个上界为底下一层的R,r<=R-1;
因此r<=min((int)sqrt(n-v), r[deep+1]-1);
同理的算法算h
h[deep] = min((int)(double)(n-v)/r[deep]/r[deep], h[deep+1]-1)+1
(2)当前的表面积+最小的可能表面积<=ans
(3)当前的体积+最小可能的体积<=n;
(4)当前的体积算出的表面积+当前已有的表面积<ans
(2*(目标体积-已有体积)/r + 已有表面积)>=ans)
完整代码:
# include <bits/stdc++.h>
using namespace std;
const int inf = 10000000;
int n, m, ans=inf;
int mins[30];
int minv[30];
int r[30], h[30];
void dfs(int deep, int s, int v)
{
if (deep==0)
{
if (v==n)
ans = min(ans, s);
return ;
}
r[deep] = min((int)sqrt(n-v), r[deep+1]-1);
while (r[deep]>=deep)
{
h[deep] = min((int)(double)(n-v)/r[deep]/r[deep], h[deep+1]-1)+1;
while (h[deep]>deep)
{
--h[deep];
if (mins[deep]+s>ans) continue;
if (minv[deep]+v>n) continue;
if (2.0*(n-v)/r[deep]+s > ans) continue;
if (deep == m) s = r[deep]*r[deep];
dfs(deep-1, 2*r[deep]*h[deep]+s, v+r[deep]*r[deep]*h[deep]);
if (deep == m) s=0;
}
--r[deep];
}
}
int main()
{
cin>>n>>m;
for (int i=1; i<=m; i++)
{
mins[i] = mins[i-1]+2*i*i;
minv[i] = minv[i-1]+i*i*i;
}
h[m+1] = r[m+1] = inf;
dfs(m, 0, 0);
if (ans == inf) cout<<"0"<<endl;
else
cout<<ans<<endl;
return 0;
}
NC16752 [NOIP2000]单词接龙
题目链接
关键点:
1、如何判断两个字符串是否重合,重合部分为多少
采用从a串尾,对着b串头对应判断的方法,从a串末尾枚举a,b串从哪个位置开始重合
要特判a串长度为1,不然重合长度会为0
2、易错点:每个字符串可以使用两次
完整代码
# include <bits/stdc++.h>
using namespace std;
int n, ans;
string s[30];
string fir;
int vis[30];
int check(string a, string b)
{
int len = min(a.length(), b.length());
for (int i=1; a.length()==1? i<=len: i<len; i++)
{
bool flag = true;
for (int j=0; j<i; j++)
{
if (a[a.size()-i+j] != b[j])
{
flag = false;
break;
}
}
if (flag == true)
return i;
}
return 0;
}
void dfs(string st, int now)
{
ans = max(ans, now);
for (int i=1; i<=n; i++)
{
if (vis[i]>1) continue;
int add = check(st, s[i]);
if (add == 0) continue;
vis[i]++;
dfs(s[i], now+s[i].length()-add);
vis[i]--;
}
}
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
cin>>s[i];
cin>>fir;
dfs(fir, 1);
cout<<ans<<endl;
return 0;
}
NC204418 新集合
题目链接
关键点:
1、对于每个数字有选或者不选,最后再判断有没有在给出的限制集合中出现的两个数
完整代码:
/**
* struct Point {
* int x;
* int y;
* Point(int xx, int yy) : x(xx), y(yy) {}
* };
*/
class Solution {
public:
int ans=0;
int use[30];
int solve(int n, int m, vector<Point>& limit) {
memset(use, 0, sizeof(use));
dfs(1, n, m, limit);
return ans;
}
void dfs(int now, int n, int m, vector<Point>& limit)
{
if (now > n)
{
for (int i=0; i<m; i++)
{
int x = limit[i].x, y = limit[i].y;
if (use[x] && use[y])
return ;
}
ans++;
return ;
}
use[now] = 1;
dfs(now+1, n, m, limit);
use[now] = 0;
dfs(now+1, n, m, limit);
}
};
NC20566 [SCOI2010]游戏
题目链接
关键点:
dfs:
1、对于装备的两个属性,我们将其连成双向边,对于连成的树去判断该树是否有来回路,有就说明可以使用该树的所有结点,为一颗树说明该树只能用n-1个结点
2、如何dfs,先标记当前结点访问过了,然后遍历所有与当前结点连接的结点,看是否和上一个访问过的结点相同,相同说明重复就continue,再看是否访问过,访问过说明有重边。该步要一定要在上一个判断的后面,因为和上一个访问过的结点相同就是访问过,而此时不能说明有重边,该dfs还有对是否有重边返回一个判断
2、先将答案ans设为总结点数+1, 遍历所有结点,找未访问过的,且该结点所在的为一颗树,那么就更新ans,在dfs里计算该树的最大结点maxn,ans = min(ans, maxn)
最后输出ans-1,因为算出的最大结点,而结点所在为一棵树,说明只能攻击结点数-1
完整代码:
# include <bits/stdc++.h>
using namespace std;
int n, maxn;
vector<int> ve[1000000+10];
int vis[1000000+10];
int dfs(int x, int fa)
{
maxn = max(maxn, x);
int flag = 0;
vis[x] = 1;
for (int i=0; i<ve[x].size(); i++)
{
int y = ve[x][i];
if (y == fa) continue;;
if (vis[y]) {flag = 1; continue;}
if (dfs(y, x)) flag = 1;
}
return flag;
}
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
{
int x, y;
cin>>x>>y;
ve[x].push_back(y);
ve[y].push_back(x);
}
int ans = n+1;
for (int i=1; i<=n; i++)
{
maxn = 0;
if (!vis[i] && !dfs(i, 0))
ans = min(ans, maxn);
}
cout<<ans-1<<endl;
return 0;
}
并查集:
fa数组存父亲结点,初始化为本身值
maxn结点存该树的最大结点,初始化为本身值
b数组存该结点所在的树是否有回路,初始化为无回路0;
1、装备的属性看作为两点连边,然后在父亲所在的maxn中更新为,两个结点的最大值,父亲所在的b更新为,如果两人结点有一个的b为1,那么就为1,两个结点中有一个结点所在的树有回路,那么两个树连接就为有回路的树
2、ans =10000+1, 最后遍历所有属性值,遍历到fa[i]=i,父亲等于本身,说明信息都在i(父亲)中,那么看b[i]是否为0,为0,说明要更新ans值为, ans = min(maxn[i], ans);
3、最后输出ans-1,原因和dfs一致