蓝书习题
AcWing 91. 最短Hamilton路径
利用状态压缩dp降低空间复杂度,我们定义dp[i][j],其中i为二进制表示,某一位为1表示已经走过了,0位没走过,j为当前处于哪个点,那么我们最后要求的就是dp[(1<<20)-1][n-1],即每一个点都走过了并且已经走到终点的路径数。需要初始化一下dp[1][0]=0,即当前已经在0点并且走了1个点。于是在任意的时刻,dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+w[k][j]),异或是因为当前转态和上一个状态只能有一个点在j,然后对于每一位是1的位k,我们可以由k转移到j,于是记录一下转移的最小值,最后即可得到最优解。当时一直没太明白本题dp的定义,现在回过头来看比之前清晰很多。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10,INF=1e8;
int w[25][25], a[1<<20][25];
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n;i++)
for (int j = 0; j < n;j++)
scanf("%d", &w[i][j]);
for (int i = 0; i < 1 << n;i++)
for (int j = 0; j < 25;j++)
a[i][j] = INF;
a[1][0] = 0;
for (int i = 0; i < 1 << n; i++)
for (int j = 0; j < n; j++)
{
if (i >> j & 1)
{
for (int k = 0; k < n; k++)
{
if (i - (1 << j) >> k & 1)
a[i][j] = min(a[i][j], a[i-(1<<j)][k] + w[k][j]);
}
}
}
printf("%d\n", a[(1 << n) - 1][n - 1]);
return 0;
}
AcWing 998. 起床困难综合症
本题是让我们在[0,m]之间选择一个数x0,使得经过n次位运算,结果ans最大。那么我们可以枚举x0的每一位看填1还是0更优。对于某一位k来说,尽量填1,当且仅当满足,k前面已经填好的更高位加上1<<k不会超过m,并且用第k位进行n次位运算,如果k=1,n次位运算之后仍为1,如果k=0,n次位运算之后仍为0,这时肯定是k=1,更优。于是我们枚举k的每一位后就可以得到最优解
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10,INF=1e8;
int n, m, t[N], op[N];
int get(int k,int now)
{
for (int i = 1; i <= n;i++)
{
int x = t[i] >> k & 1;
if(op[i]==1)
now &= x;
else if(op[i]==2)
now |= x;
else if(op[i]==3)
now ^= x;
}
return now;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n;i++)
{
char s[5];
scanf("%s%d", s, &t[i]);
if(s[0]=='A')
op[i] = 1;
else if(s[0]=='O')
op[i] = 2;
else if(s[0]=='X')
op[i] = 3;
}
int res = 0, ans = 0;
for (int i = 29; i >= 0;i--)
{
int res0 = get(i, 0);
int res1 = get(i, 1);
if(res+(1<<i)<=m&&res0<res1)
{
res += 1 << i;
ans += res1 << i;
}
else
ans += res0 <<= i;
}
printf("%d\n", ans);
return 0;
}
AcWing 95. 费解的开关
对于第一行我们可以枚举每一位的状态,一共是2^5个状态,当某一位是1时我们就按下当前这一位,注意:这里枚举的只是第一行按开关的32种方式,和输入的第一行无关。然后我们固定这一行不动,只是通过该行的下一行来改变它的状态,于是我们可以枚举2-4行,最后检查一下如果第5行还有开关是0的话说明该方法是不合法的,在合法的方法里取最小值得到最优解。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=10,INF=1e8;
int n;
char a[N][N];
int dx[5] ={0,1,-1,0,0},dy[5] = {0, 0, 0, 1, -1};
void change(int x,int y)
{
for(int i=0;i<5;i++)
{
int a1=x+dx[i],b1=y+dy[i];
if(a1>=0&&a1<5&&b1>=0&&b1<5)
{
if(a[a1][b1]=='1') a[a1][b1]='0';
else a[a1][b1]='1';
}
}
}
int slove()
{
int ans = INF;
for (int i = 0; i < 1 << 5;i++)
{
int res = 0,ok=1;
char back[N][N];
memcpy(back, a, sizeof a);
for (int j =0; j < 5;j++)
{
if(i>>j&1)
{
res++;
change(0, j);
}
}
for (int j = 0; j < 4;j++)
{
for (int k = 0; k < 5;k++)
{
if(a[j][k]=='0')
{
res++;
change(j + 1, k);
}
}
}
for (int j = 0; j < 5;j++)
{
if(a[4][j]=='0')
{
ok = 0;
break;
}
}
if(ok) ans=min(ans,res);
memcpy(a, back, sizeof back);
}
if(ans>6)
ans = -1;
return ans;
}
int main()
{
int n;
scanf("%d", &n);
while(n--)
{
for (int i = 0; i < 5; i++)
cin >> a[i];
printf("%d\n", slove());
}
return 0;
}
AcWing 98. 分形之城
首先我们得观察一下图形,我们发现第n级的左上角是由n-1级的图形顺时针旋转90度的得到的,右上角则是直接平移,右下角是由右上角向下平移得到,左下角则是逆时针旋转90度再向下平移2倍n=1的长度再-1得到的,其他三个比较好理解但对于左下角还是需要了解一下矩阵的旋转平移啥的。观察发现每一级的边长其实就是2n,城市的个数则是4n,我们可以把当前一级的城市看成是由4个n-1级城市组成的,如果要确定两个城市的距离的话得先确定他们分别位于哪个城市最后通过递归取得城市的坐标,最后计算距离。
最开始做这一题的时候畏难情绪很强,后面要再反复回顾。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
const int N=1e5+10,INF=1e8;
double dist(PLL a,PLL b)
{
return sqrt((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
}
PLL get(ll n,ll x)
{
if(n==0)
return {0, 0};
ll len = pow(2,n-1), cnt = pow(4,n-1);
PLL pos = get(n - 1, x % cnt);
ll xx = pos.first, yy = pos.second;
ll z = x / cnt;
if(z==0)
return {yy, xx};
if(z==1)
return {xx, yy + len};
if(z==2)
return {xx + len, yy + len};
return {2 * len - yy - 1, len - xx - 1};
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
ll n,a,b;
scanf("%lld%lld%lld", &n, &a, &b);
a--, b--;
PLL ans1 = get(n, a);
PLL ans2 = get(n, b);
printf("%.lf\n", dist(ans1, ans2)*10);
}
return 0;
}
AcWing 100. IncDec序列
思路:一开始想的是每个数都减去一个数,以为是平均值,后面想到差分就让每个数减去它前面一个构造了差分数组,因为我们最终是要让序列的每一个数都一样,所以我们让b[1]=a[1],让b[2-n]变成0,预处理出2-n的正数和z和负数和f,一部分的操作次数就是现在z和f消去一部分即min(z,f),剩下一部分我们考虑与a[1]相消去,于是答案为min(z,f)+abs(z-f),这里有abs(z-f)+1种排列方式,+1是因为z和f可能相等,那么a[1]本身就是一种排列方式了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10,INF=1e8;
ll a[N],b[N],n;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n;i++)
scanf("%lld",&a[i]);
b[1] = a[1];
ll z = 0, f = 0,ans=0;
for (int i = 2; i <= n;i++)
{
b[i] = a[i] - a[i - 1];
if(b[i]>0)
z += b[i];
else
f -= b[i];
}
ans += min(z, f)+abs(z-f);
printf("%lld\n%lld", ans, abs(f - z) + 1);
return 0;
}
AcWing 101. 最高的牛
思路:如果两头牛要相互看的见的话它们之间的牛的身高必须都小于他们,于是我们对每次输入的牛让它们之间的牛身高都-1,利用差分数组记录一下每对牛之间的相对关系,最后转化成前缀和再加上最高的牛的身高。
一开始判重用的是int数组结果超内存了,改用map就过了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int N=1e4+10,INF=1e8;
map<pair<int, int>,int> mp;
int ans[N];
int main()
{
int n, p, h, m;
scanf("%d%d%d%d", &n, &p, &h, &m);
for (int i = 1; i <= m;i++)
{
int a, b;
scanf("%d%d", &a, &b);
if(a>b)
swap(a, b);
if(mp[{a,b}])
continue;
mp[{a,b}] = 1;
ans[a + 1]--;
ans[b]++;
}
for (int i = 1; i <= n;i++)
{
ans[i] +=ans[i-1];
printf("%d\n", ans[i]+h);
}
return 0;
}
AcWing 102. 最佳牛围栏
思路:要求平均值*1000,应该用浮点数二分,为了简便计算,对于每个数我们都先减去二分的平均值,这下问题可以转化成在一个序列中求长度不超过L的非负子段和。因为每块地最多也就只有2000头牛,假设每块地都有2000头牛的话,那么二分答案的最大值也就是2000,因此可以缩小二分的范围,在二分的过程中,我们不必枚举每次的最小值,只需要在i增长的过程中记录下当前的最小值就可以,同时不断更新答案:b[i]-mmin。
一开始减平均值的时候想错了,没在二分的时候减。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10,INF=1e8;
int n, m;
double a[N], b[N];
int check(double mid)
{
double mmin = INF, mmax = -INF;
for (int i = 1; i <= n;i++)
b[i] = b[i - 1] + a[i] - mid;
for (int i = m; i <= n;i++)
{
mmin = min(mmin, b[i - m]);
mmax = max(mmax, b[i] - mmin);
}
return mmax >= 0;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n;i++)
{
scanf("%lf", &a[i]);
}
double l = 0, r = 2000;
while(r-l>1e-5)
{
double mid = (l + r) / 2;
if(check(mid))
l = mid;
else
r = mid;
}
printf("%d", int(r * 1000));
return 0;
}