逆序对
1.利用归并排序
void merge_sort(int l,int mid,int r)
{
int k = r-l+1;
int i = l,j=mid+1;
for(int m=l;m<=r;m++)
{
if(j>r||i<=mid&&a[i]<a[j])
b[m] = a[i++];
else
{
b[m] = a[j++];
num+=mid-i+1;//关键
}
}
for(int i=l;i<=r;i++)
{
a[i] = b[i];
}
}
void merge(int l,int r)
{
int mid = 0;
if(l<r)
{
mid = (l+r)/2;
merge(l,mid);
merge(mid+1,r);
merge_sort(l,mid,r);
}
}
- b数组不要new,new会降低效率
2.利用树状数组
转发:树状数组
Ultra-QuickSort(poj2299)
const int maxn=500005;
int n;
int aa[maxn]; //离散化后的数组
int c[maxn]; //树状数组
struct Node{
int v;
int order;
}in[maxn];
int lowbit(int x)
{
return x&(-x);
}
void update(int t,int value)
{
int t;
for(;t<=n;t+=lowbit(t))
{
c[t]+=value;
}
}
int getsum(int x)
{
int temp=0;
for(;x>=1;x-=lowbit(x))
{
temp+=c[x];
}
return temp;
}
bool cmp(Node a ,Node b)
{
return a.v<b.v;
}
int main()
{
int i,j;
while(scanf("%d",&n)==1 && n)
{
//离散化
for(i=1;i<=n;i++)
{
scanf("%d",&in[i].v);
in[i].order=i;
}
sort(in+1,in+n+1,cmp);
for(i=1;i<=n;i++) aa[in[i].order]=i;//非常关键的一步,记录该处相对大小即可
//树状数组求逆序
memset(c,0,sizeof(c));
long long ans=0;
for(i=1;i<=n;i++)
{
update(aa[i],1);//更新树状数组
ans+=i-getsum(aa[i]);//求逆序对数
}
cout<<ans<<endl;
}
return 0;
}
M*N Puzzle
- 移动空格,左右之间不改变逆序对数的大小。
- 上下移动会改变逆序对数
- 由于最后逆序对数为0,所以改变的逆序对数也为0
- 列数要分奇偶进行判断
int main()
{
while(~scanf("%d%d",&n,&m)&&n&&m)
{
int index = 0;
num = 0;
bool flag = 0;
int k=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
int tmp;
scanf("%d",&tmp);
if(tmp==0)
index = i;
else
a[k++] = tmp;
}
}
merge(0,n*m-2);
if(m%2==1)
{
if(num%2==0)
flag = 1;
}
else
{
if((num+n-index+1)%2==0)
flag = 1;
}
if(flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
贪心
证明方法
- 微扰(邻项交换):一般以“排序”作为贪心策略的证明
- 范围缩放:证明任何对局部最优策略作用范围的扩展都不会造成整体结果变差。
- 决策包容性:证明在任意局面下,做出局部最优决策以后,在问题状态空间中的可达集合包含了做出其他任何其他决策后的可达集合。换言之,这个局部最优策略提供的可能性包含其他所有策略提供的可能性。
- 反证法
- 数学归纳
Sunscreen(POJ3614)
题意:c个奶牛,第一奶牛一个最低承受度,一个最高承受度。防晒霜有L种,效果为spf[i],有cover[i]瓶。求最多可以满足多少头奶牛进行日光浴。
分析:
- 按照奶牛的最低承受度降序排列。
- 按照防晒霜的效果降序排列。
- 尽可能的让奶牛选择较大的防晒霜。
证明:
- 如果不选。留给以后也不会使数量增加。
- 如果选小的,可能会影响之后。
#define MAX 2501
struct node
{
int min,max;
}a[MAX];
struct node2
{
int spf,num;
}b[MAX];
bool cmp(node a,node b)
{
return a.min>b.min;
}
bool cmp2(node2 a,node2 b)
{
return a.spf>b.spf;
}
int main()
{
int c,l;
while(cin>>c>>l)
{
for(int i=0;i<c;i++)
{
scanf("%d%d",&a[i].min,&a[i].max);
}
for(int i=0;i<l;i++)
{
scanf("%d%d",&b[i].spf,&b[i].num);
}
sort(a,a+c,cmp);
sort(b,b+l,cmp2);
int ans = 0;
for(int i=0;i<c;i++)
{
for(int j=0;j<l;j++)
if(b[j].num!=0&&b[j].spf<=a[i].max&&b[j].spf>=a[i].min)
{
ans++;
b[j].num--;
break;
}
}
cout<<ans<<endl;
}
return 0;
}
Stall Reservations(poj3190)
好像之前做够类似的题目。但是并没有用优先队列优化,这次不用过不去了。
const int maxn = 60000;
int n,use[maxn];
struct node
{
int s,e,pos;
bool operator<(const node&a)const
{
if(e==a.e)
return s>a.s;
return e>a.e;//优先队列里面的优先级(让结束早的在堆顶)
}
}a[maxn];
priority_queue<node> q;
bool cmp(node a,node b)
{
if(a.s==b.s)
return a.e<=b.e;
return a.s<b.s;
}
int main()
{
while(cin>>n)
{
for(int i=0;i<n;i++)
{
scanf("%d%d",&a[i].s,&a[i].e);
a[i].pos = i;
}
sort(a,a+n,cmp);
q.push(a[0]);
int ans = 1;
use[a[0].pos] = 1;
for(int i=1;i<n;i++)
{
if(!q.empty()&&q.top().e<a[i].s)
{
use[a[i].pos] = use[q.top().pos];//如此巧妙
q.pop();//旧的就淘汰掉
}
else
{
ans++;
use[a[i].pos] = ans;
}
q.push(a[i]);
}
cout<<ans<<endl;
for(int i=0;i<n;i++)
cout<<use[i]<<endl;
while(!q.empty())
q.pop();
}
return 0;
}
领接表数组表示
- head数组存放下标对应的点其中的一条边序号(下标)
- next数组存放是接着的一条边序号(ver下标)
- ver数组边的终点
void add(int x,int y,int z)
{
ver[++tot] = y,edge[tot] = z;
next[tot] = head[x],head[x] = tot;
}
//遍历从x出发的所有边.
for(int i=head[x];i;i = head[i])
{
int y = ver[i];
z = edge[i];
}
- 对于无向图,直接当做插入两条边即可
- 也可以利用成对变换的特性,初始化tot为1,然后2和3,4和5···依次为一对边。计算时用异或就可以了。