算法竞赛入门经典(第2版)-刘汝佳-第八章例题解题源码(C++语言)(部分)

例题8-1

采用直接构造法,也就是经验方法求解,通过猜想感觉经验求解。没有一个通用的模板。

#include
using namespace std;
const int maxn=100;
int pancake[maxn],ans[maxn];
int len=0,ansi=0;
int flip(int index)
{
	int tmp[maxn];
	for(int i=0;i<=index;i++)
	{
		tmp[i]=pancake[index-i];
	}
	for(int i=0;i<=index;i++)
	{
		pancake[i]=tmp[i];
	}
	
} 
void findmaxnumber(int l,int j)
{
	int maxnumber=pancake[0],maxindex=0;
	for(int i=1;i>in)
		{
			if(flag>0)
				cout<<" ";
			pancake[len++]=in;
			cout<

例题8-2

本题目也是采用经验法,进行直接构造。

#include
using namespace std;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int n;
	while(cin>>n)
	{
		cout<<"2"<<" "<

例题8-3

本题最好使用hash,但是一直不能设计好hash函数,使用map会超时,而本题使用的存储方式,一定要将fl声明为全局变量,如果是局部变量会报错,编译器不会提供那么大的空间用于存储。因为有大量的数据读入,所以使用ios::sync_with_stdio(false); 关闭同步,增加速度。

#include
using namespace std;
const int maxn=5000;
long long fl[4005*4005];
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	ios::sync_with_stdio(false);
	long long A[maxn],B[maxn],C[maxn],D[maxn];
	int m,flag=0;
	cin>>m;
	while(m--)
	{
		if(flag)
		{
			cout<>n;
		long long ans=0;
		for(int i=0;i>A[i]>>B[i]>>C[i]>>D[i];
		}
		
		int  r,c,z;
		long long len=0;
		for(int i=0;i

例题8-4

枚举超时,枚举代码:

#include
using namespace std;
const int maxn=5000+100;
int xl[maxn],xr[maxn],yl[maxn],yr[maxn];
int visX[maxn],visY[maxn],n;
bool dfsx(int *A,int cur)
{
	if(cur==n)
	{
		return true;
	}
	for(int i = xl[cur];i<=xr[cur];i++)
	{
		if(visX[i]) continue;
		else
		{
			visX[i]=1;
			A[cur]=i;
			if(dfsx(A,cur+1)) 
			return true;
			visX[i]=0;
		}
	}
	return false;
}
bool dfsy(int *A,int cur)
{
	if(cur==n)
	{
		return true;
	}
	for(int i = yl[cur];i<=yr[cur];i++)
	{
		if(visY[i]) continue;
		else
		{
			visY[i]=1;
			A[cur]=i;
			if(dfsy(A,cur+1)) 
			return true;
			visY[i]=0;
		}
	}
	return false;
}

int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	while(cin>>n&&n)
	{
		memset(visX,0,sizeof(visX));
		memset(visY,0,sizeof(visY));
		for(int i=0;i>xl[i]>>yl[i]>>xr[i]>>yr[i];
		}
		int X[maxn],Y[maxn];
		if((!dfsx(X,0))||(!dfsy(Y,0)))
		cout<<"IMPOSSIBLE"<

所以,本题可以采用贪心策略,因为贪心策略的使用,也只是一种猜想,当然在编程前能证明是最好的。本次采用的贪心策略是先将各区间进行按照xr从小到达排序,xr相等的,xl较小的排在前面。排序后的各点在格子区间里面选择尽量小的数作为自己的行数(列数)。目的是让先选择的车,选择的行(列)对后面的车的选择影响最小。

#include
using namespace std;
const int maxn=5000+100;
struct rook
{
	int xl,xr,yl,yr;
	int id;	
};
rook rooks[maxn],rookssort[maxn];
bool cmpx(rook a,rook b)
{
	if(a.xr!=b.xr)	
		return a.xr>n&&n)
	{
		int ansx[maxn],ansy[maxn],findx=1,findy=1;
		int visitx[maxn],visity[maxn];
		memset(visitx,0,sizeof(visitx));
		memset(visity,0,sizeof(visity));
		for(int i=0;i>rooks[i].xl>>rooks[i].yl>>rooks[i].xr>>rooks[i].yr;
		}
		sort(rooks,rooks+n,cmpx);
		for(int i=0;i

例题8-5

本题目通过等价转换后,代码变的简单。将一个大问题变小。

#include
using namespace std;
int main()
{
	int n;
	while(cin>>n&&n)
	{
		long long ans=0,a,last=0;
		for(int i=0;i>a;
			ans+=abs(last);
			last+=a;
		}
		cout<

例题8-6

本题采用极点扫描法,参考了代码仓库的代码,对关键的扫描部分进行了注释。

// UVa1606 Amphiphilic Carbon Molecules
// Rujia Liu
// To make life a bit easier, we change each color 1 point into color 0.
// Then we only need to find an angle interval with most points. See code for details.
#include
#include
#include
#include
using namespace std;

const int maxn = 1000 + 5;

struct Point {
  int x, y;
  double rad; // with respect to current point
  bool operator<(const Point &rhs) const {
    return rad < rhs.rad;
  }
}op[maxn], p[maxn];

int n, color[maxn];

// from O-A to O-B, is it a left turn?
bool Left(Point A, Point B) {
  return A.x * B.y - A.y * B.x >= 0;
}

int solve() {
  if(n <= 2) return 2;
  int ans = 0;

  // pivot point
  for(int i = 0; i < n; i++) {
    int k = 0;

    // the list of other point, sorted in increasing order of rad
    for(int j = 0; j < n; j++)
      if(j != i) {
        p[k].x = op[j].x - op[i].x;
        p[k].y = op[j].y - op[i].y;
        if(color[j]) { p[k].x = -p[k].x; p[k].y = -p[k].y; }
        p[k].rad = atan2(p[k].y, p[k].x);
        k++;
      }
    sort(p, p+k);
    // sweeping. cnt is the number of points whose rad is between p[L] and p[R]
    int L = 0, R = 0, cnt = 2;
    while(L < k) {
    	//注意这个循环体内部cnt不会重置,那么在这个循环中,求解白点的数量,是通过
		//在相对坐标系中,以原点和p[L]之间的连线作为分割线,以L=0为初始状态,通过扫描的
		//方法(判断P[R]是否符合要求)计算出左侧白点的数量,当L=1的时候,在L=0时的白点加上符合L=1时分割线的白点,减去
		//不符合要求的点(实际上只有一个点)。 
      if(R == L) { R = (R+1)%k; cnt++; } 
      while(R != L && Left(p[L], p[R])) { R = (R+1)%k; cnt++; } //增加符合要求的点 
      cnt--;//减去不符合要求的白点。 
      L++;
      ans = max(ans, cnt);
    }
  }
  return ans;
}

int main() {
	freopen("datain.txt","r",stdin);
  while(scanf("%d", &n) == 1 && n) {
    for(int i = 0; i < n; i++)
      scanf("%d%d%d", &op[i].x, &op[i].y, &color[i]);
    printf("%d\n", solve());
  }
  return 0;
}


例题8-7

本题使用两种方法,进行求解。算法复杂度相同,引入了滑动窗口的概念。

第一种:

#include
using namespace std;
const int maxn = 1000000+5;
int A[maxn];
int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=0;i s;
		int L=0,R=0,ans=0;
		while(R

第二种:滑动窗口

#include
using namespace std;
int A[maxn],last[maxn];
map cur;
int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);	
		cur.clear();
		for(int i=0;i

例题8-8

本题基本采用等价转化的思想,并使用set来进行实现。本题列出代码仓库的代码。

// UVa1471 Defense Lines
// Rujia Liu
// Algorithm 1: use STL set to maintain the candidates.
// This is a little bit more intuitive, but less efficient (than algorithm 2)
#include
#include
#include
using namespace std;
const int maxn = 200000 + 5;
int n, a[maxn], f[maxn], g[maxn];
	
struct Candidate {
	int a, g;
	Candidate(int a, int g):a(a),g(g) {}
	bool operator < (const Candidate& rhs) const {
	return a < rhs.a;
	}
};
	
set s;
	
int main() {
		freopen("datain.txt","r",stdin);
		int T;
		scanf("%d", &T);
		while(T--) {
			scanf("%d", &n);
			for(int i = 0; i < n; i++)
			scanf("%d", &a[i]);
			if(n == 1) { printf("1\n"); continue; }
	
			// g[i] is the length of longest increasing continuous subsequence ending at i
			g[0] = 1;
			for(int i = 1; i < n; i++)
				if(a[i-1] < a[i]) g[i] = g[i-1] + 1;
				else g[i] = 1;
	
			// f[i] is the length of longest increasing continuous subsequence starting from i
			f[n-1] = 1;
			for(int i = n-2; i >= 0; i--)
				if(a[i] < a[i+1]) f[i] = f[i+1] + 1;
				else f[i] = 1;
	
			s.clear();
			s.insert(Candidate(a[0], g[0]));
			int ans = 1;
			for(int i = 1; i < n; i++) {
			Candidate c(a[i], g[i]);
			set::iterator it = s.lower_bound(c); 
		// first one that is >= c
			bool keep = true;
			if(it != s.begin()) {
			Candidate last = *(--it); 
		// (--it) points to the largest one that is < c
			int len = f[i] + last.g;
			ans = max(ans, len);
			s.insert(c);
		}
	
	if(keep) {
		s.erase(c); // if c.a is already present, the old g must be <= c.g
	
		it = s.find(c); // this is a bit cumbersome and slow but it's clear
		it++;
	while(it != s.end() && it->a > c.a && it->g <= c.g) s.erase(it++);
	}
	}
		printf("%d\n", ans);
	}
		return 0;
	}


例题8-9

本题目参考书中解答和代码仓库。

// UVa1451 Average
	// Rujia Liu
	#include
	using namespace std;
	
	const int maxn = 100000 + 5;
	
	int n, L;
	char s[maxn];
	int sum[maxn], p[maxn]; // average of i~j is (sum[j]-sum[i-1])/(j-i+1)
	
	// compare average of x1~x2 and x3~x4
	int compare_average(int x1, int x2, int x3, int x4) {
	  return (sum[x2]-sum[x1-1]) * (x4-x3+1) - (sum[x4]-sum[x3-1]) * (x2-x1+1);
	}
	
	int main() {
	  int T;
	  scanf("%d", &T);
	
	  while(T--) {
	    scanf("%d%d%s", &n, &L, s+1);
	
	    sum[0] = 0;
	    for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + s[i] - '0';
	
	    int ansL = 1, ansR = L;
	
	    // p[i..j) is the sequence of candidate start points
	    int i = 0, j = 0;
	    for (int t = L; t <= n; t++) { // end point
	      while (j-i > 1 && compare_average(p[j-2], t-L, p[j-1], t-L) >= 0) j--; // remove concave points
	      p[j++] = t-L+1; // new candidate
	
	      while (j-i > 1 && compare_average(p[i], t, p[i+1], t) <= 0) i++; // update tangent point
	
	      // compare and update solution
	      int c = compare_average(p[i], t, ansL, ansR);
	      if (c > 0 || c == 0 && t - p[i] < ansR - ansL) {
	        ansL = p[i]; ansR = t;
	      }
	    }
	    printf("%d %d\n", ansL, ansR);
	  }
	  return 0;
	}


例题8-10

本题目采用的二分法和贪婪算法结合,通过二分法找到最大值尽量小的那个数,在通过贪心法从右向左尽量搜索,每个子序列在小于找到的那个数的情况,尽量长。这样就能保证S(1)尽量小,满足条件。

#include
using namespace std;
typedef long long ll;
const int maxn=600;
ll m,k,p[maxn],slash[maxn];
;
bool judge(ll mid,ll& maxnum)
{
	for(int i=0;i=k-i-1;j--)
		{
			if(i!=k-1)
			{
				if(sum+p[j]<=mid)
				{
					sum+=p[j];
					slash[i+1]=j;
				}
				else
				{
					break;
				}
					
			}
			else
			{
				sum+=p[j];
				slash[i+1]=j;
			}
			
		}
		maxnum=max(maxnum,sum);
	}
	if(maxnum<=mid) return true;
	else return false;	
}

ll bsearch(ll x,ll y)
{
	ll mid;
	slash[0]=m;
	while(x>T;
	while(T--)
	{
		ll lb=0,ub=0,mid,maxnum=0;
		cin>>m>>k;
		for(int i=0;i>p[i];
			ub+=p[i];
		}
		mid = bsearch(lb,ub);
		judge(mid,maxnum);
		int j=k-1;
		for (int i=0;i0)
			{
				cout<<"/"<<" ";
				j--;
			}
			cout<0)
			{
				cout<<"/"<<" ";
				j--;
			}
		cout<

例题8-11

贪心算法,每次选择最小的两个数进行相加,并将他们的和压入优先队列。

#include
using namespace std;
const int maxn=6000;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int n;
	while(cin>>n&&n)
	{
		int tmp;
		priority_queue,greater> q;
		for(int i=0;i>tmp;
			q.push(tmp);
		}
		int ans = 0;
		for(int i=0;i

习题8-12

我开始并没有使用提供的方法,我们可以发现规律,第k小时第i行。i>2^(k-1)的时候,红气球数量等于第k-1小时第i行的红气球数量,如果i<=2^(k-1)时,红气球数量等于第k-1小时第i行的红气球数量的2倍。这样求出每行再累加起来就可以得到答案。时间复杂度为O(k*(b-a)).经过UVa测试,LTE超时,还是不得不使用书中O(k)的思路。

第一种:TLE

#include
using namespace std;
typedef long long ll;
ll cntr(ll k, ll a)
{
	if(k==0) return 1;
	else if(a>(1<<(k-1))) 
	{
		return cntr(k-1, a-(1<<(k-1)));	
	}
	else
	{
		return cntr(k-1, a)*2;
	}

}


int main()
{
	//freopen("datain.txt","r",stdin);
//	freopen("dataout.txt","w",stdout);
	int T,rnd=1;
	cin>>T;
	while(T--)
	{
		ll k,a,b;
		ll sum=0;
		cin>>k>>a>>b;
		for(ll i=a;i<=b;i++)
		{
			sum+=cntr(k,i);
		}
		cout<<"Case "<

第二种:

通过求解书中的f函数或者g函数都能求解,下面以g函数为例。本题使用了递推。

#include
using namespace std;
long long c(int k)  
{  
    return k == 0 ? 1 : c(k - 1) * 3;  
}  
  
long long g(int k, int i)  
{  
    if (i == 0) {  
        return 0;  
    }  
    if (k == 0) {  
        return 1;  
    }  
  
    int k2 = 1 << (k - 1); 
    if (i >= k2) {  
        return 2 * g(k - 1, i - k2) + c(k - 1);  
    }  
    else {  
        return g(k - 1, i);  
    }  
}  
  
int main()  
{  	

	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
    ios::sync_with_stdio(false);  
    int T;  
    cin >> T;  
    int kase = 0;  
    while (T--) {  
        int k, a, b;  
        cin >> k >> a >> b;  
        cout << "Case " << ++kase << ": ";   
        cout << g(k, (1 << k) - a + 1) - g(k, (1 << k) - b) << endl;  
    }  
  
    return 0;  
}  

例题8-13

模拟法,主要是要从中挖掘和分析出一些限制条件,避免没有必要的枚举,节省时间。


#include
using namespace std;
typedef long long ll;
const ll maxn=100010;
int ps[maxn],qs[maxn];
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int T,rnd=1;
	cin>>T;
	while(T--)
	{
		cout<<"Case "<>n;
		for(ll i=0;i>ps[i];
		for(ll i=0;i>qs[i];
		int su=1;
		for(ll i=0;i=qs[j])
				{
					gas-=qs[j++];
					if(j == n){j %= n;flag = 1;}
					//表示已经遍历了N个点,如果能够成功那么cnt==n
					//如果失败,那么证明以i...n作为起点也不会成功 
					cnt++;		
				}
				else
				{
					i = j+1;break;
				}	
			}
			if(cnt == n)
			{
				cout<<"Possible from station "<

例题8-14

本题目自己对题意理解不深,使用的是代码仓库的代码,以供参考。

// UVa1607 Gates
// Rujia Liu
#include
#include
using namespace std;

const int maxm = 200000 + 5;

int n, m;

struct Gates {
  int a, b, o;
} gates[maxm];

// returns the output of input 000..0111...1 (there are k 0's)
int output(int k) {
  for(int i = 1; i <= m; i++) {
    int a = gates[i].a;
    int b = gates[i].b;
    int va = a < 0 ? -a > k : gates[a].o;
    int vb = b < 0 ? -b > k : gates[b].o;
    gates[i].o = !(va && vb);
  }
  return gates[m].o;
}

// returns k such that 
// 1. output(k) = output(n)
// 2. output(k-1) = output(0)
int solve(int vn) {
  int L = 1, R = n;
  while(L < R) {
    int M = L + (R-L)/2;
    if(output(M) == vn) R = M; else L = M+1;
  }
  return L;
}

int main() {
  int T;
  scanf("%d", &T);
  while(T--) {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++)
      scanf("%d%d", &gates[i].a, &gates[i].b);
    int v0 = output(0);
    int vn = output(n);
    if(v0 == vn) {
      for(int i = 1; i <= n; i++) printf("0");
    } else {
      int x = solve(vn);
      for(int i = 1; i < x; i++) printf("0");
      printf("x");
      for(int i = x+1; i <= n; i++) printf("1");
    }
    printf("\n");
  }
  return 0;
}

例题8-15

本题通过滑动窗口加枚举的方式,总的而言,主要是枚举那边理解有些困难,其他还好。

代码参考:点击打开链接

#include 
#include
#include
using namespace std;

const int N = 1e5 + 5;

int s, n, a[N], vis[N];
bool flag[N];
int ans;

void init() {
    cin >> s >> n;
    int num = 0;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        if (i < s) {   //对前面的s个进行分析
            if (vis[a[i]]) num++;   //统计前s个中重复的数字
            vis[a[i]]++;
        }
    }

    for (int i = 0; i < n; i++) {
        //如果num=0,说明前s个中没有重复的数字,那么第一个数字可以作为循环的开始
        if (num == 0) flag[i] = true;

        //窗口开始滑动
        if (vis[a[i]] == 2) num--;    //如果此时最左边的数为重复了的数,num需要减1
        vis[a[i]]--;

        int k = i + s;     //新数字进入滑动窗口
        if (k >= n) continue;
        if (vis[a[k]]) num++;   //如果已经出现过
        vis[a[k]]++;
    }
}

bool judge(int x) {   
    for (int i = x; i < n; i += s)
        if (!flag[i]) return false;
    return true;
}

void solve() {
    memset(vis, 0, sizeof(vis));

    ans = 0;
    for (int i = 0; i < s; i++) {
        if (judge(i)) ans++;
        if (i >= n) continue;
        //从左往右依次遍历,如果当前a[i]前面已经出现过,那么前面必须会有开头,此时必须结束循环
        if (vis[a[i]]) break;
        vis[a[i]]++;
    }
}

int main() {
    //freopen("D:\\txt.txt", "r", stdin);
    int t;
    cin >> t;
    while (t--) {
        memset(flag, 0, sizeof(flag));
        memset(vis, 0, sizeof(vis));
        init();
        solve();
        cout << ans << endl;
    }
    return 0;
}


例题8-16 

本题目按照书中的方法,没有问题,这里参考了代码仓库的代码,并进行了注释,便于学习。 

#include
#include
using namespace std;

const int maxn = 200000 + 5;
int A[maxn], prev[maxn], next[maxn];
map cur;

inline bool unique(int p, int L, int R) {
  return prev[p] < L && next[p] > R;
  //如果与在p位置的元素相等的前一个元素的位置小于L,后一个元素位置大于R。
  //则说明在这个子序列中唯一。 
}

bool check(int L, int R) {
  if(L >= R) return true;
  for(int d = 0; L+d <= R-d; d++) {
  	//从头开始找,尝试第一个元素是不是唯一 
    if(unique(L+d, L, R))
      return check(L, L+d-1) && check(L+d+1, R);
      //如果子序列没有唯一元素,返回false 
    if(L+d == R-d) break;
    //从头开始找,尝试最后一个元素是不是唯一 
    if(unique(R-d, L, R))
      return check(R-d+1, R) && check(L, R-d-1);
	  //如果子序列没有唯一元素,返回false 
  }
  return false;//如果没有唯一元素,返回false. 
}

int main() {
	//freopen("datain.txt","r",stdin);
  	int T, n;
  	scanf("%d", &T);
  	while(T--) {
    scanf("%d", &n);
    cur.clear();
    for(int i = 0; i < n; i++) {
      scanf("%d", &A[i]);
      if(!cur.count(A[i])) prev[i] = -1;//如果A[i]没有出现过,则赋值为1. 
      else prev[i] = cur[A[i]];//如果出现过,则将前一个的索引存入pre。 
      cur[A[i]] = i;//保存A[i]的索引i。 
    }
    cur.clear();
    //同理找到后一个相等元素的位置 
    for(int i = n-1; i >= 0; i--) {
      if(!cur.count(A[i])) next[i] = n;
      else next[i] = cur[A[i]];
      cur[A[i]] = i;
    }

    if(check(0, n-1)) printf("non-boring\n");
    else printf("boring\n");
  }
  return 0;
}


你可能感兴趣的:(算法竞赛)