2.26-3.4第一周练习

1.二分搜索

    1)从有序数组中查找值 2)假定一个解判断是否可行

    3)最大化最小值 POJ3273-Monthly Expense

     题意:n天每天有不同的花费,将n天分成m组,问各组费用之和的最大值最小是多少。

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 100005
#define INF 0x3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n, m;
ll a[maxn], su;
bool judge(ll x)
{
	int num = 1;
	ll sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += a[i];
		if (sum > x)
		{
			num++;
			sum = a[i];
		}
	}
	return num <= m;
}
int main()
{
	while (scanf("%d%d", &n, &m) != EOF)
	{
		su = 0;
		ll l = -INF;
		for (int i = 0; i < n; i++)
		{
			scanf("%lld", &a[i]), su += a[i];
			if (l < a[i])l = a[i];
		}
		ll r = su, mid;
		while (r >= l)
		{
			mid = (l + r) / 2;
			if (judge(mid))r = mid - 1;
			else l = mid + 1;
		}
		printf("%lld\n", mid);
	}
	return 0;
}

    4)最大化平均值

    5)查找第k大值

    upper_bound(a,a+n,x)查找数组a中大于x的第一个地址值

    lower_bound(a,a+n,x)查找数组a中不小于x的第一个地址值

2.尺取法

反复推进区间的开头和结尾,求出满足给定条件的最小区间,时间复杂度O(n)。

POJ2566-Bound Found:给出一个数列a和一个数t,求a的一个连续的子序列,使得子序列之和的绝对值最接近t。

题解:要使用尺取法首先一般要保证其单调性,显然不能直接用尺取法。需要进行一定的转化找到单调性,a显然是不能改变的,就对a求前缀和。将前缀和排序后用尺取法逼近t即可。由于要求的是绝对值,区间左端的实际下标不一定要小于右端,只需要保证二者不相等即可。

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 100005
#define INF 0x3f3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n, m, t, a[maxn];
struct node
{
	int sum;
	int id;
}e[maxn];
bool cmp(node a, node b){return a.sum < b.sum;}
void solve()
{
	int l = 0, r = 1;
	int minn = INF, ans, ansl, ansr;
	while (r <= n&&minn)
	{
		int tmp = e[r].sum - e[l].sum;
		if (abs(tmp - t) < minn)
		{
			minn = abs(tmp - t);
			ans = tmp;
			ansl = e[l].id;
			ansr = e[r].id;
		}
		if (tmp < t)r++;
		if (tmp > t)l++;
		if (l == r)r++;
	}
	if (ansl > ansr)swap(ansl, ansr);
	printf("%d %d %d\n", abs(ans), ansl + 1, ansr);
}
int main()
{
	while (scanf("%d%d", &n, &m))
	{
		if (n == 0 && m == 0)break;
		e[0].sum = e[0].id = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			if (i == 0)e[i].sum = a[i];
			else e[i].sum = e[i - 1].sum + a[i];
			e[i].id = i;
		}
		sort(e, e + n + 1, cmp);
		for (int i = 0; i < m; i++)
		{
			scanf("%d", &t);
			solve();
		}
	}
	return 0;
}

3.反转(开关问题) POJ1222-Extended Lights Out:某些灯泡亮着,当按下某一个灯泡开关时,它本身和四周的四个灯泡的状态都会改变,问需要按下哪些开关才能将所有的灯泡都熄灭。

题解:多次反转是多余的,因此每个位置最多反转一次,此外反转的顺序对结果是没有影响的。对于一个n*m的矩阵,共有2^(nm)种状态,需要简化。先用2^n枚举第一行的反转方法,此时能够改变(1,1)的只有(2,1),可以直接确定(2,1)是否反转,以此类推确定下每一个位置。但此时只能保证前面n-1行已经全部熄灭,最后看经过这些操作最后一行是否都熄灭了,如果都熄灭则说明此方案可行。总的时间复杂度为O(mn2^n)。

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 10
#define INF 0x3f3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int a[maxn][maxn], t, kase=0;
int ans[maxn][maxn];
int pos[5][2] = { 0,1,-1,0,0,-1,0,0,1,0 };
int main()
{
	scanf("%d", &t);
	while (t--)
	{
		for (int i = 0; i < 5; i++)
			for (int j = 0; j < 6; j++)scanf("%d", &a[i][j]);
		for (int i = 0; i < (1 << 6); i++)
		{
			memset(ans, 0, sizeof(ans));
			for (int j = 0; j < 6; j++)ans[0][j] = (i >> j) & 1;
			for (int p = 0; p < 4; p++)
			{
				for (int j = 0; j < 6; j++)
				{
					int num = a[p][j];
					for (int k = 0; k < 5; k++)
					{
						int x = p + pos[k][0];
						int y = j + pos[k][1];
						if (x >= 0 && x < 5&&y >= 0 && y < 6)num += ans[x][y];
					}
					if (num % 2 != 0)ans[p + 1][j] =1;
				}
			}
			bool flag = 1;
			for (int j = 0; j < 6; j++)
			{
				int num = a[4][j];
				for (int k = 0; k < 5; k++)
				{
					int x = 4 + pos[k][0];
					int y = j + pos[k][1];
					if (x >= 0 && x < 5 && y >= 0 && y < 6)num += ans[x][y];
				}
				if (num % 2 != 0)
				{
					flag = 0;
					break;
				}
			}
			if (flag)
			{
				printf("PUZZLE #%d\n",++kase);
				for (int j = 0; j < 5; j++)
					for (int k = 0; k < 6; k++)printf(k == 5 ? "%d\n" : "%d ", ans[j][k]);
				break;
			}
		}
	}
	return 0;
}

4.弹性碰撞:在发生弹性碰撞是,可以认为二者交换了身份信息,擦肩而过。

POJ2674-Linear World:在数轴上给定范围,范围内有若干个点以相同速度运动并发生弹性碰撞,问最后一个离开给定范围的是哪个点及时间。

题解:时间是很容易求解的,距边缘最远的点到达边缘的时间就是最后的时间。至于最后离开的人,需要看最远的人和哪些人发生了碰撞。由于所有人速度相同,只会和方向相反的人发生碰撞,因此找出最远的人的方向上与其方向相反的人数cnt,此人方向上不论初始方向为何,第cnt个人即为所求的人。

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 32005
#define INF 0x3f3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n,u;
double l, v;
struct node
{
	int id;
	char pos[5], name[25];
	double x, maxt;
}e[maxn];
bool cmp(node a, node b){return a.x < b.x;}
int main()
{
	while (scanf("%d", &n)&&n)
	{
		scanf("%lf%lf", &l, &v);
		for (int i = 0; i < n; i++)
		{
			e[i].id = i;
			scanf("%s%lf%s", e[i].pos, &e[i].x, e[i].name);
			if (e[i].pos[0] == 'p' || e[i].pos[0] == 'P')e[i].maxt = (l - e[i].x) / v;
			else e[i].maxt = e[i].x / v;
		}
		sort(e, e + n, cmp); 
		double maxx = -1;
		for (int i = 0; i < n; i++)
			if (e[i].maxt > maxx)
			{
				u = i;
				maxx = e[i].maxt;
			}
		if (e[u].pos[0] == 'p' || e[u].pos[0] == 'P')
		{
			int cnt = 0;
			for (int i = u + 1; i < n; i++)
				if (e[i].pos[0] == 'n' || e[i].pos[0] == 'N')cnt++;
			printf("%13.2lf %s\n", (int)(maxx * 100) / 100.0, e[u + cnt].name);
		}
		else
		{
			int cnt = 0;
			for (int i = u ; i >= 0; i--)
				if (e[i].pos[0] == 'p' || e[i].pos[0] == 'P')cnt++;
			printf("%13.2lf %s\n", (int)(maxx * 100) / 100.0, e[u - cnt].name);
		}
	}
	return 0;
}

5.折半枚举:当无法枚举全部状态是,可枚举一半的状态,将另一半二分。

POJ3977-Subset:给定一个数组,求一个子集使得子集元素之和的绝对值最小。

#include<cstring>
#include<cstdio>
#include<map>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 45
#define INF 0x3f3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n;
ll a[maxn];
map<ll, int>m;
ll Min(ll a, ll b){return a < b ? a : b;}
ll Abs(ll x){return x > 0 ? x : (-x);}
int main()
{
	while (scanf("%d", &n)&&n)
	{
		for (int i = 0; i < n; i++)scanf("%lld", &a[i]);
		m.clear();
		pair<ll, int>ans = make_pair(1e18, 0);
		for (int i = 1; i < (1 << n / 2); i++)
		{
			ll sum = 0;int cnt = 0;
			for (int j = 0; j < n / 2; j++)
				if ((i >> j) & 1){sum += a[j];cnt++;}
			if (!m[sum] || m[sum] > cnt)m[sum] = cnt;
			pair<ll, int>tmp = make_pair(Abs(sum), cnt);
			if (ans > tmp)ans = tmp;
		}
		for (int i = 1; i < (1 << (n - n / 2)); i++)
		{
			ll sum = 0;int cnt = 0;
			for (int j = 0; j < (n - n / 2); j++)
				if ((i >> j) & 1){sum += a[j + n / 2];cnt++;}
			pair<ll, int>tmp = make_pair(Abs(sum), cnt);
			if (ans > tmp)ans = tmp;
			map<ll, int>::iterator k = m.lower_bound(-sum);
			if (k != m.end())
			{
				tmp = make_pair(Abs((*k).first + sum), (*k).second + cnt);
				if (ans > tmp)ans = tmp;
			}
			if (k != m.begin())k--;
			if (k != m.end())
			{
				tmp = make_pair(Abs((*k).first + sum), (*k).second + cnt);
				if (ans > tmp)ans = tmp;
			}
		}
		printf("%lld %d\n", ans.first, ans.second);
	}
	return 0;
}

6.链表反转/重排

PAT L2-002/022  链表的处理方法与spfa遍历边的方法类似,不断地遍历当前节点的后继直至到末尾。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 100050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
int st, n, w, x, y;
int flag[maxn], a[maxn], b[maxn];
struct node
{
  int pre;
  int nxt;
}m[maxn];
int main()
{
  scanf("%d%d", &st, &n);
  int na = 0, nb = 0;
  for (int i = 0; i < n; i++)
  {
    scanf("%d%d%d", &w, &x, &y);
    m[w].pre = x, m[w].nxt = y;
  }
  w = st;
  while(w!=-1)
  {
    int u = abs(m[w].pre);
    if (!flag[u])
    {
      flag[u] = 1;
      a[na++] = w;
    }
    else b[nb++] = w;
    w = m[w].nxt;
  }
  printf("%05d %d ", a[0], m[a[0]].pre);
  for (int i = 1; i < na; i++)
    printf("%05d\n%05d %d ", a[i], a[i], m[a[i]].pre);
  printf("-1\n");
  if (nb > 0)
  {
    printf("%05d %d ", b[0], m[b[0]].pre);
    for (int i = 1; i < nb; i++)
      printf("%05d\n%05d %d ", b[i], b[i], m[b[i]].pre);
    printf("-1\n");
  }
  return 0;
}

7. PAT L2-001 紧急救援

题意:无向图,点和边都有权值,求一条路径使得边权和最小,点权和最大。输出最短路条数,最大点权和及相应的路径。

题解:核心是Dijkstra算法。用num[i]表示从起点到i的最短路条数,wei[i]表示从起点到i的最大点权,pre[i]记录i的前驱节点。当dis[v]==dis[u]+maze[u][v]时,更新num[v]+=num[u],wei[v]=max(wei[v],wei[u]+w[v])。当dis[v]<dis[u]+maze[u][v]时,更新dis[v],同时num[v]=num[u],wei[v]=wei[u]+w[v]。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 505
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
int n, m, s, d, a, b, x;
int maze[maxn][maxn], pre[maxn];
int num[maxn], dis[maxn], wei[maxn];
int flag[maxn], w[maxn];
void init()
{
  memset(flag, 0, sizeof(flag));
  memset(pre, -1, sizeof(pre));
  for (int i = 0; i < n; i++)
  {
    for (int j = 0; j < n; j++)
      maze[i][j] = INF;
    dis[i] = INF;
  }
}
void dijkstra()
{
  dis[s] = 0, wei[s] = w[s], num[s] = 1;
  for (int i = 0; i < n; i++)
  {
    int u = -1;
    for (int j = 0; j < n; j++)
    {
      if (!flag[j] && (u == -1 || dis[j] < dis[u]))
        u = j;
    }
    if (u == -1)break;
    flag[u] = 1;
    for (int v = 0; v < n; v++)
    {
      if (!flag[v] && maze[u][v] != INF)
      {
        if (dis[v] > dis[u] + maze[u][v])
        {
          dis[v] = dis[u] + maze[u][v];
          num[v] = num[u];
          pre[v] = u;
          wei[v] = wei[u] + w[v];
        }
        else if (dis[v] == dis[u] + maze[u][v])
        {
          num[v] += num[u];
          if (wei[v] < wei[u] + w[v])
          {
            wei[v] = wei[u] + w[v];
            pre[v] = u;
          }
        }
      }
    }
  }
}
void printpath()
{
  int p[maxn], x = d, no = 0;
  for (; x != -1; x = pre[x])
    p[no++] = x;
  for (int i = no - 1; i >= 0; i--)
    printf(i == 0 ? "%d\n" : "%d ", p[i]);
}
int main()
{
  scanf("%d%d%d%d", &n, &m, &s, &d);
  init();
  for (int i = 0; i < n; i++)
    scanf("%d", &w[i]);
  for (int i = 0; i < m; i++)
  {
    scanf("%d%d%d", &a, &b, &x);
    maze[a][b] = maze[b][a] = min(x, maze[a][b]);
  }
  dijkstra();
  printf("%d %d\n", num[d], wei[d]);
  printpath();
  return 0;
}

8.根据二叉树的前序遍历和中序遍历重建二叉树

前序遍历的第一个节点是数的根节点,在中序遍历中找到这个根节点,那么它在中序遍历中左侧的节点都是左子树的节点,右侧都是右子树的节点。若左子树长度为len,那么前序遍历中根节点之后的len个节点都是左子树的节点。然后对左子树找到根,递归地重复上述操作。

PAT L2-011 镜像层序遍历用bfs输出。

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 556
#define INF 0x3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int n;
int in[maxn], out[maxn];
int l[maxn], r[maxn];
int build(int l1, int r1, int l2, int r2)
{
  if (l1 > r1)return 0;
  int root = out[l2];
  int tmp = l1;
  while (in[tmp] != root)tmp++;
  int cnt = tmp - l1;
  l[root] = build(l1, tmp - 1, l2 + 1, r2 + cnt);
  r[root] = build(tmp + 1, r1, l2 + cnt + 1, r2);
  return root;
}
void bfs(int root)
{
  queue<int>q;
  int now, flag = 0;
  q.push(root);
  while (!q.empty())
  {
    now = q.front();
    q.pop();
    if (flag)printf(" ");
    printf("%d", now);
    flag = 1;
    if (r[now])q.push(r[now]);
    if (l[now])q.push(l[now]);
  }
  printf("\n");
}
int main()
{
  scanf("%d", &n);
  for (int i = 0; i < n; i++) scanf("%d", &in[i]);
  for (int i = 0; i < n; i++) scanf("%d", &out[i]);
  int p = build(0, n - 1, 0, n - 1);
  bfs(out[0]);
  return 0;
}

9.PAT L2-014 求最少的下降序列数目。根据Dilworth定理,最小下降序列数目等于最长上升子序列长度。

10.PAT L2-018 多项式除法

#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 10050
#define INF 0x3f3f3f3f
#define eps 1e-6
using namespace std;
typedef long long ll;
int x,lena,lenb,maxa=-1,maxb=-1,cntc,cnta;
double y,a[maxn],b[maxn],c[maxn];
int input(int len,double *arr, int *maxx)
{
    for(int i=0;i<len;++i)
    {
        scanf("%d%lf",&x,&y);
        arr[x]=y;
        *maxx=max(*maxx,x);
    }
}
void clearzoro(int &cnt,int be,double *arr)
{
    cnt=0;
    for(int i=be;i>=0;--i)
    {
        if(!fabs(arr[i])<1e-8)
            fabs(arr[i])<0.05?arr[i]=0.0:cnt++;
    }
}
void output(int cnt,int be,double *arr)
{
    if(cnt==0) puts("0 0 0.0");
    else
    {
        printf("%d",cnt);
        for(int i=be;i>=0;--i)
            if(!fabs(arr[i])<1e-8) printf(" %d %.1lf",i,arr[i]);
        puts("");
    }
}
int main()
{
    scanf("%d",&lena);
    input(lena,a,&maxa);
    scanf("%d",&lenb);
    input(lenb,b,&maxb);
    for(int i=maxa;i>=maxb;--i)
    {
        c[i-maxb]=a[i]/b[maxb];
        for(int j=maxb;j>=0;--j)a[j+i-maxb]-=b[j]*c[i-maxb];
    }
    clearzoro(cntc,maxa-maxb,c);
    clearzoro(cnta,maxb,a);
    output(cntc,maxa-maxb,c);
    output(cnta,maxb-1,a);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值