当一个准备考研的acmer,必定是坑队友的货,BUT WHATEVER ENJOY THE BREAK ,DO A GOOD TAKE,SHARE THE CAKE
2013-10- 8
哈希与哈弗曼树
POJ3349 (数字哈希的运用)
/*
题目大意:就是在给出的雪花当中判断有没有至少一样的一对雪花。
利用哈希找出少数总和长度相等的雪花,然后在通过顺时针和逆时针枚举6^2长度的雪花
进行比较看雪花是否相等即可
思路:在10W片雪花中直接比较雪花明显不可,但是经过哈希之后可以边插入边比较。
*/
#include<iostream>
using namespace std;
const __int64 prime=999983; // 10n内最大的素数(本题n=10W)
class{
public:
__int64 len[6]; //6瓣叶子的长度
}leaf[100001];
typedef class HashTable{
public:
__int64 len[6]; //记录6瓣叶子的长度信息
HashTable* next; //用于冲突时开放寻址
HashTable() //Initial
{
next=0;
}
}Hashtable;
Hashtable* hash[prime+1];//模拟链表的头指针的作用 (边表结点也是沿用这个定义)
/*计算第k个雪花的关键字key*/
__int64 compute_key(int k){
__int64 key=0;
for(int i=0;i<6;i++){
key+=(leaf[k].len[i]) % prime;
key%=prime; //利用同余模定理计算key,避免出现大数
}
return ++key; //键值后移1位,把key的范围从0~999982变为 1~999983
}//数字A哈希就是 利用A%素数进行确定唯一的键值。但每个键值不一定对应唯一的数,
//这就是冲突,这有点像离散的处理
/*从顺时针方向判断两片雪花是否相同*/
bool clockwise(Hashtable* p,int k){
for(int j=0;j<6;j++){ //顺时针转动j格
bool flag=true;
for(int i=0;i<6;i++)
if(leaf[k].len[i] != p->len[(i+j)%6]){
flag=false;
break;
}
if(flag)
return true;
}
return false;
}
/*从逆时针方向判断两片雪花是否相同*/
bool counterclockwise(Hashtable* p,int k){
for(int j=0;j<6;j++){ //逆时针转动j格
bool flag=true;
for(int i=0;i<6;i++)
if(leaf[k].len[i] != p->len[(5-i-j+6)%6]){
flag=false;
break;
}
if(flag)
return true;
}
return false;
}
/*把第k个雪花信息插入HashTable*/
/*当插入的位置已存在其他雪花信息时,顺便比较*/
bool insert(int k)
{
__int64 key=compute_key(k);
if(!hash[key]){
Hashtable* temp=new Hashtable;
for(int i=0;i<6;i++)
temp->len[i]=leaf[k].len[i];
hash[key]=temp; //保存key对应的地址
}
else //地址冲突,开放寻址,顺便比较
{
Hashtable* temp=hash[key];
if(clockwise(temp,k) || counterclockwise(temp,k)) //检查雪花是否相同
return true;
while(temp->next) {//寻址
temp=temp->next;
if(clockwise(temp,k) || counterclockwise(temp,k)) //检查雪花是否相同
return true;
}
temp->next=new Hashtable; //申请空间,保存新雪花信息
for(int i=0;i<6;i++)
temp->next->len[i]=leaf[k].len[i];
}
return false;
}
int main(int i,int j){
int n; //雪花数
while(cin>>n){
/*Initial*/
memset(hash,0,sizeof(hash)); // 0 <-> NULL
/*Input*/
bool flag=false; //记录输入过程中是否出现了相同的雪花
for(i=1;i<=n;i++){
for(j=0;j<6;j++)
scanf("%I64d",&leaf[i].len[j]);
/*Hash*/
if(!flag) //当前还没有出现相同的雪花
flag=insert(i);
//若出现相同的雪花,则还需后续输入,但不再处理
}
/*Output*/
if(flag)
cout<<"Twin snowflakes found."<<endl;
else
cout<<"No two snowflakes are alike."<<endl;
}
return 0;
}
POJ1200 (字符串哈希的运用)
/*
简单的rabin-karp
把出现过的每个字母映射到对应的数字,
这样字符串就变成相应的nc进制数,
然后把它转换成10进制,并放入hash[]中,
如果是第一次放入,则总数加一。
题目大意:就是给出母串,然后问有多少个长度为n的子串(是串!)是不一样的。
*/
#include <stdio.h>
#include <fstream>
#include <string.h>
#include <memory.h>
#include <iostream>
using namespace std;
#define MAX 16000005
char s[MAX];
int ascii[256];
int hash[MAX];
int main()
{
//freopen("acm.txt","r",stdin);
int n,nc;
int i,j;
int len,cnt,sum;
while(scanf("%d%d",&n,&nc)!=EOF)
{
cnt=0;
memset(ascii,0,sizeof(ascii));
memset(hash,0,sizeof(hash));
scanf("%s",s);
len=strlen(s);
for(i=j=0; i<len; i++) //根据字符出现的顺序,给字符标号0,1,2,。。。。
{
if(!ascii[s[i]]) ascii[s[i]]=j++;
//比如daababac,那么d,a,b,c分别对应:0,1,2,3,4
if(j==nc) break; //开始这里写成nc-1,wa了好多次。
}
len=len-n+1;//这里是个小技巧
for(i=0; i<len; i++) //把子串映射到hash数组中
{
sum=0;
for(j=0; j<n ; j++) //子串长度
{
sum=sum*nc+ascii[s[i+j]]; //转换成nc进制数,想想为什么要这样(暂时当定理)
//这里之所以是s+j,因为j是从0开始的,如果j是从i开始就不用i+j了
}
if(!hash[sum])
{
hash[sum]=1;
cnt++;
}
}
printf("%d\n",cnt);
}
return 0;
}
HDU2527 (哈弗曼树的应用)
/*
题目大意:给出字符串,以字符串的出现频率为权值,
求字符串编码的最小压缩空间(字符编码最小的空间值)与给定的一个数字的比较,
如果小于给定的数字,则输出**
*/
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <queue>
using namespace std;
#define MAXN 1001
priority_queue<int , vector<int>, greater<int> > q;
char str[MAXN];
int huf[MAXN];
int n;
void solve()
{
int ans = 0, a, b;
while(q.size() != 1)
{
a = q.top(); q.pop();
b = q.top(); q.pop();
q.push(a+b);
ans += (a+b);
}
printf(ans <= n?"yes\n":"no\n");
}
void init()
{
while(!q.empty()) q.pop();
memset(huf, 0, sizeof(huf));
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
init();
scanf("%d", &n);
scanf("%s", str);
for(int i = 0; str[i]; i++)
{
huf[str[i]-'a']++;
}
for(int i = 0; i < 26; i++) if(huf[i])
{
q.push(huf[i]);
}
if(q.size() == 1)//特殊处理优先队列只有一个数值的情况
{
int a = q.top(); q.pop();
printf(a <= n?"yes\n":"no\n");
continue;
}
solve();
}
return 0;
}
POJ1521(哈弗曼树的应用)
/*
bit pattern 位模式
compression ratio 压缩比例
题目大意:求出每位都是用8位存储的位空间,求出用哈弗曼树压缩结果的位空间
求出前者比上后者的结果
*/
#include"iostream"
#include"queue"
#include"string"
#include"functional"
using namespace std;
#define TOP 999999
int main()
{
char a[10000];
while(gets(a)&&strcmp(a,"END\0")!=0)//以END为输入结尾
{
int len=strlen(a);
int i;
int strsize[1000];
int sum0=len*8;
int sum1=0;
priority_queue<int,vector<int>,greater<int> >x;
for(i=0;i<1000;i++)
strsize[i]=TOP; //top这里是记载字符是否出现过的标志:败笔
for(i=0;i<len;i++){
int t=a[i];
if(strsize[t]==TOP)
strsize[t]=0;
++strsize[t];
}
for(i=0;i<1000;i++){
if(strsize[i]!=TOP){
x.push(strsize[i]);
}
}
if(x.size()==1)//特别注意队列中只有一个元素的情况*/
{
int a=x.top();
printf("%d %d %.1lf\n",sum0,a,(double)sum0/a);
continue;
}
while(x.size()>1)
{
int a,b;
a=x.top(); x.pop();
b=x.top(); x.pop();
sum1+=a+b;
x.push(a+b);
}
printf("%d %d %.1lf\n",sum0,sum1,(double)sum0/sum1);
}
return 0;
}
字符串处理
KMP模板再总结
/*KMP算法
普通的朴素匹配算法效率是O(n*m)的算法 ,
n是主串长度,m是模式串长度
kmp时间效率是O(n+m)的算法。
即,KMP是B串查找算法,即查找B串在母串中的位置。
如果有10^3字符个B串,且长度n和m都不超过10^3,则可以考虑KMP算法。
如果用朴素的字符串匹配则明显不行(10^9的时间效率)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;
int len1,len2;
char a[5000000],b[5000000],aa[5000000],bb[5000000];
int next[5000000];
void get_next()
{
int i=1,j=0;
next[1]=0;
//就是说模式串1~n的话,
//那么正常情况下求出i<len2即可,即next:1~n,
//但是如果要求模式串的最长前缀等于后缀的长度的话,那么求出的next:1~n+1
//而令i<=len2就是求出最后的一次
while(i<=len2)//i<len2.
{
if(j==0||bb[i]==bb[j]){ ++i; ++j; next[i]=j; }
else j=next[j];
}
}
int kmp()
{
int i=1,j=1;//开始匹配的两个下标
while(i<=len1&&j<=len2)//任意一个指针超了都会结束匹配
{
if(j==0||aa[i]==bb[j]){ ++i; ++j; }
else j=next[j];
}
if(j>len2)return i-len2;//模式串的指针超了标志着查找成功
return 0;
}
int main()
{
while(cin>>a>>b)
{
len1=strlen(a);
len2=strlen(b);
for(int i=1;i<=len1;i++)
{
aa[i]=a[i-1];//母串和模式串都是从下标为1的地址开始
}
for(int i=1;i<=len2;i++)//next数组也是
{
bb[i]=b[i-1];
}
get_next();
cout<<"***"<<len2<<endl;
for(int i=0;i<len2+2;i++)
cout<<i<<" "<<next[i]<<endl;
int tmp=kmp();
if(tmp)
cout<<"yes "<<tmp<<endl; //返回的是子串在母串的开始位置
else
cout<<"not found"<<endl;
}
return 0;}
kmp next数组优化
/*KMP算法
普通的朴素匹配算法效率是O(n*m)的算法 ,
n是主串长度,m是模式串长度
kmp时间效率是O(n+m)的算法。
即,KMP是B串查找算法,即查找B串在母串中的位置。
如果有10^3字符个B串,且长度n和m都不超过10^3,则可以考虑KMP算法。
如果用朴素的字符串匹配则明显不行(10^9的时间效率)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;
int len1,len2;
char a[5000000],b[5000000],aa[5000000],bb[5000000];
int next[5000000];
void get_next()
{
int i=1,j=0;
next[1]=0;
//就是说模式串1~n的话,
//那么正常情况下求出i<len2即可,即next:1~n,
//但是如果要求模式串的最长前缀等于后缀的长度的话,那么求出的next:1~n+1
//而令i<=len2就是求出最后的一次
while(i<=len2)//i<len2.
{
if(j==0||bb[i]==bb[j]){
++i; ++j;
if(bb[i]!=bb[j])next[i]=j;
else next[i]=next[j];//优化在这!
}
else j=next[j];
}
}
int kmp()
{
int i=1,j=1;//开始匹配的两个下标
while(i<=len1&&j<=len2)//任意一个指针超了都会结束匹配
{
if(j==0||aa[i]==bb[j]){ ++i; ++j; }
else j=next[j];
}
if(j>len2)return i-len2;//模式串的指针超了标志着查找成功
return 0;
}
int main()
{
while(cin>>a>>b)
{
len1=strlen(a);
len2=strlen(b);
for(int i=1;i<=len1;i++)
{
aa[i]=a[i-1];//母串和模式串都是从下标为1的地址开始
}
for(int i=1;i<=len2;i++)//next数组也是
{
bb[i]=b[i-1];
}
get_next();
cout<<"***"<<len2<<endl;
for(int i=0;i<len2+2;i++)
cout<<i<<" "<<next[i]<<endl;
int tmp=kmp();
if(tmp)
cout<<"yes "<<tmp<<endl; //返回的是子串在母串的开始位置
else
cout<<"not found"<<endl;
}
return 0;}
POJ3630(静态字典树的应用)
/*字典树
字典树的一个重要性质:每个字符串组成的集合A(每个字符串都不相同)
根节点到每个叶子节点组成的集合B
有A=B的函数满射关系
插入过程:建立一颗满射的树,由于对i字符串而言,
插入时间效率就是len(i),所以整个插入过程的时间效率就是
字符串的长度的总和。
字典树入门应用:如何求一个字符串S的所有不同子串
(1)比较容易想到的是把S的所有子串拆下来,然后放到哈希里面处理,
遇到冲突则进行比较即可(保证冲突少是算法效率的关键)
(2)还有就是可以把所有子串(A集合)拆下来插入到字典树里面,字典树
结点的总数就是子串的个数(性质,如图)
*/
#include <iostream>
using namespace std;
const int MAXN = 10010;
struct trie
{
bool flag;
int next[10];
}tree[60000];
struct alph
{
int len;
char ch[11];
}str[MAXN];
bool sign;
int loc;
void build()//对于每一个样例建立一棵树,但是静态建立就简单多了
{
tree[1].flag = false;//开一个tree以下标为1的作为根节点
for (int i = 0; i < 10; ++i)//根节点下面有10个子节点
tree[1].next[i] = -1;
}
void insert(alph ss)
{
int r = 1, len = ss.len;
for (int j = 0; j < len; ++j)
{
int index = ss.ch[j] - '0';
if (tree[r].next[index] == -1)
{
tree[r].next[index] = loc;
/*由于是静态数组,所以对于每个子节点被创建时都是以顺序逻辑的情况存下来的
也就是类似完全二叉树的静态创建时,用一维数组把整棵树保存下来,
loc数组就是保存整棵树的结点的作用。
而孩子关系则通过next数组来表示
*/
tree[loc].flag = false;
/*
flag为true的时候就是字符串结尾的标志。否则还不是字符串结尾
*/
if (j == len - 1)
tree[loc].flag = true;
for (int m = 0; m < 10; ++m)
tree[loc].next[m] = -1;
r = loc;
loc++;
//cout<<"j "<<j<<"loc "<<loc<<endl;
}
else
{
r = tree[r].next[index];
/*前者表示的长的匹配短的,后者标志短的匹配长的情况
把后者删了加个排序也可以*/
if (tree[r].flag || (j == len - 1))
{
sign = true;
return;
}
}
}
}
int main()
{
int nCases, n;
scanf("%d", &nCases);
while (nCases--)
{
scanf("%d", &n);
getchar();
for (int i = 0; i < n; ++i)
{
gets(str[i].ch);
str[i].len = strlen(str[i].ch);
}
build();
sign = false;
loc = 2;//因为1已经是作为根节点使用了,所以从2开始
for (int i = 0; i < n; ++i)
{
if (!sign)
insert(str[i]);
else break;
//cout<<"******"<<endl;
}
if (sign) puts("NO");
else puts("YES");
}
return 0;
}
POJ2513(动态字典树 +并查集+ 欧拉回路)
/*
注意:时间长的一般是用动态建树空间花费小些,但是要花费时间new节点
时间短的内存允许的一般是静态建树,免去new节点的时间
动态字典树:
题目大意:就是问一些有颜色的双端棒子可不可能一笔画,
颜色相同的两端可以接在一块。
这就是欧拉回路问题:不重复的经过所有的顶点和边,且经过一次。
由离散数学可以知道:
无向图存在欧拉路的充要条件为:
① 图是连通的;
② 所有节点的度为偶数,或者有且只有两个度为奇数的节点。
对于检查图是否连通可以通过并查集求得,但是并查集处理的是序号,
所以对颜色要进行处理成数字序号,但是这里的字符串数量大,用map有可能超时
所以用字典树对字符串进行处理。
总的解题步骤:
(1)输入两个颜色字符串,经过字典树处理,求出颜色序号,标记颜色出现的次数
(2)同时合并这两个颜色代表的序号
(3)每次输入即每次插入每次合并,输入完成之后:判断图是否连通,判断度为
奇数的节点数的个数
*/
#include<iostream>
using namespace std;
const int large=500000; //25W条棒子,有50W个端点
class TrieTree_Node //字典树结点
{
public:
//单词的结尾的标记
bool flag; //标记到字典树从根到当前结点所构成的字符串是否为一个(颜色)单词
int id; //当前颜色(结点)的编号
TrieTree_Node* next[27];
TrieTree_Node() //initial
{
flag=false;
id=0;
memset(next,0,sizeof(next)); //0 <-> NULL
}
}root; //字典树根节点
int color=0; //颜色编号指针,最终为颜色总个数
int degree[large+1]={0}; //第id个结点的总度数
int ancestor[large+1]; //第id个结点祖先
/*寻找x结点的最终祖先*/
int find(int x)
{
if(ancestor[x]!=x)
ancestor[x]=find(ancestor[x]); //路径压缩
return ancestor[x];
}
/*合并a、b两个集合*/
void union_set(int a,int b)
{
int pa=find(a);
int pb=find(b);
ancestor[pb]=pa; //使a的祖先 作为 b的祖先
return;
}
//利用字典树构造字符串s到编号int的映射
int trie_hash(char *s)
{
//根节点,每进行一次插入都把工作指针指向根节点
TrieTree_Node* p=&root; //从TrieTree的根节点出发搜索单词(单词不存在则创建)
int len=0;
while(s[len]!='\0')
{
int index=s[len++]-'a'; //把小写字母a~z映射到数字的1~26,作为字典树的每一层的索引
if(!p->next[index]) //当索引不存在时,构建索引
p->next[index]=new TrieTree_Node;
p=p->next[index];
}
if(p->flag) //颜色单词已存在
return p->id; //返回其编号
else //否则创建单词
{
p->flag=true;
p->id=++color;
return p->id; //返回分配给新颜色的编号
}
}
int main()
{
/*Initial the Merge-Set*/
for(int k=1;k<=large;k++) //初始化,每个结点作为一个独立集合
ancestor[k]=k; //对于只有一个结点x的集合,x的祖先就是它本身
/*Input*/
char a[11],b[11];
while(cin>>a>>b)
{
/*Creat the TrieTree*/
int i=trie_hash(a);
int j=trie_hash(b); //得到a、b颜色的编号
/*Get all nodes' degree*/
degree[i]++;
degree[j]++; //记录a、b颜色出现的次数(总度数)
/*Creat the Merge-Set*/
union_set(i,j);
}
/*Judge the Euler-Path*/
int s=find(1); //若图为连通图,则s为所有结点的祖先
//若图为非连通图,s为所有祖先中的其中一个祖先
int num=0; //度数为奇数的结点个数
for(int i=1;i<=color;i++)
{
if(degree[i]%2==1)
num++;
if(num>2) //度数为奇数的结点数大于3,欧拉路必不存在
{
cout<<"Impossible"<<endl;
return 0;
}
if(find(i)!=s) //存在多个祖先,图为森林,不连通
{
cout<<"Impossible"<<endl;
return 0;
}
}
if(num==1) //度数为奇数的结点数等于1,欧拉路必不存在
cout<<"Impossible"<<endl;
else //度数为奇数的结点数恰好等于2或不存在,存在欧拉路
cout<<"Possible"<<endl;
return 0;
}
HDU2222 AC自动机
/*HDU2222 AC自动机
给出一串很长的字符串10^5以上
然后给出10^5模式串,KMP吃不消
AC自动机算法:
(1)利用模式串建立trie树
(2)构造失败指针
(3)模式匹配过程(类似于KMP)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
using namespace std;
int n;
char key[55];
char des[1000005];
struct node{
node * next[26];
node * fail;
int cnt;
node(){
cnt=0;
fail=0;
memset(next,0,sizeof(next));
}
};
/*
根据模式串建立trie树
*/
node * insert(node * root ){
//结点指针 不能每次
node * p=root;//工作指针
node * q;
int i=0;
while(key[i]){
int index=key[i]-'a';
//cout<<"index "<<index<<endl;
//cout<<"p->next[index] "<<p->next[index]<<endl;
//还是设置为空的时候申请一个结点,其他的和已经存在的一样,都要移动p和i
if(!p->next[index])
p->next[index]=new node();//指针之后还是指针
p=p->next[index];//p=q;
i++;//遍历串
}
p->cnt+=1;
return root;
}
/*
根据建立好的trie树求出fail指针
*/
void ac_auto(node * root){//其实就是建立失败指针
root->fail=0;//根结点的失败指针为0
queue<node*>que;// 弄为全局变量?
//que.clear();
que.push(root);
//工作指针
while(!que.empty()){
node * tmp=que.front();
node * p=0;
que.pop();//记得pop
for(int i=0;i<26;i++){
//!tmp->next[i]会发生错误
if(tmp->next[i]==0)continue;//如果是没有这个字母就跳出
if(tmp==root){ //如果根结点的子节点特殊情况处理
tmp->next[i]->fail=root;
que.push(tmp->next[i]);
//cout<<"i "<<i<<endl;
continue;
}
//cout<<"i "<<i<<endl;
//node * tmp=p;
p=tmp->fail;
//p=p->fail;//tmp当前的是父亲结点,工作的是儿子结点,为其找失败指针
while(p!=0){//最远的前缀就是在根结点的子结点才找到相应的前缀
if(p->next[i]!=0){
tmp->next[i]->fail=p->next[i];
break;
}//顺着父亲结点的失败指针一直往上找,
//如果不是根结点的话,那么这些结点都是和父亲结点有相同的字符
//知道找到,或者到跟结点为止.(包括根结点还要判断一次)
p=p->fail;
}
if(p==0)//即是找不到这样的前缀,那么失败指针指向root
tmp->next[i]->fail=root;
que.push(tmp->next[i]);//统一上两种情况的入队姿势
//system("pause");
}
}
}
/*
根据失败指针模式匹配母串
*/
void query(node * root){
node * q=root;
int len=strlen(des);
int index;
int sum=0;//最后的结果
for(int i=0;i<len;i++){
index=des[i]-'a';
//if(q->next[index]==0)continue;//貌似不能直接跳过,因为还要考虑下q指针
while(q->next[index]==0 && q!=root){
q=q->fail;//如果当前没有这个子结点,那么就根据失败指针倒退,
//知道找到或者回到根结点
}
q=q->next[index];
q=(q==0)?root:q;
node * tmp=q;
while(tmp!=root && tmp->cnt!=-1){
sum+=tmp->cnt;
tmp->cnt=-1;
tmp=tmp->fail;
}
}
cout<<sum<<endl;
}
int main(){
int test;
cin>>test;
while(test--){
scanf("%d",&n);
node * root=new node();
for(int i=0;i<n;i++){
scanf("%s",key);
insert(root);
}
scanf("%s",des);
ac_auto(root);
query(root);
};
//system("pause");
return 0;
}
2013 -10- 9
最短路径&&线段树
最短路径小结之朴素dij算法
/*最短路径小结之朴素dij算法
不断找一条 起点v->未标记的点vt 这样的一条路径,
而这样的一条路径还要满足: v到未标记节点距离中最短
算法效率是O(n^2).
PS:邻接表
num=1;
void init(int a ,int b,int w){
next[num]=head[a];//一开始的next[N]都是等于0,
如果这个head【a】等于0的话(意味着这个顶点还没延伸出边)
把这几个数组看成指针会好理解很多
head[a]=num;
adj[num]=b;
vv[num]=w;
num++;
}
总结:head数组总是记录着该头结点最迟加入的边结点的下标,
然后通过这个边结点的next数组找到该头结点的下一个边结点,
直到遍历结束(碰到0)
next数组和adj数组总是和边数的编号一致的。
*/
#include<iostream>
using namespace std;
int t[10][10];
int dist[10];
int flag[10];
#define Infty 2100000000;
void fi(int n)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
t[i][j]=Infty;
}
void dijista(int n,int a,int b)
{
for(int i=0;i<n;i++)
{
dist[i]=t[a][i];
flag[i]=0;
}
flag[a]=1;
for(int i=0,k;i<n;i++) //n个顶点就找出n次目前最短的然后更新路径
{
int min=Infty;
for(int j=0;j<n;j++)
{
if(flag[j]!=1&&dist[j]<min) //找出当前最短路径,加入S集合中
{
k=j;
min=dist[j];
}
}
printf("dist[k]: %d k: %d\n ",dist[k],k);
flag[k]=1;
if(k==b)
{
printf("a->b: %d\n ",dist[k]);
goto END;
}
for(int j=0;j<n;j++) //用最新的S集合来更新于V集合中点的距离
if(flag[j]!=1&&dist[j]>dist[k]+t[k][j])
dist[j]=dist[k]+t[k][j];
}
END:;
}
int main()
{
int n,m,a,b,p,q,w;
while(scanf("%d%d%d%d",&n,&m,&a,&b))//n顶点,m边数,a起点,b终点
{
fi(n);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&p,&q,&w);
t[p][q]=w;
t[q][p]=w;
}
dijista(n,a,b);
}
system("pause");
return 0;}
dij的邻接表实现
/*
dij的邻接表实现
vector模拟邻接表
*/
#include <vector>
using namespace std;
typedef struct edge
{
int v;
int w;
}edge;
#define narray 1001
const int maxData = 0x7fffffff;
vector<edge> adj[narray]; //邻接表
int m,n; //m为边数,n为顶点数
int d[narray];
bool final[narray];
void Dij(int src)
{
int i,j;
int mintemp,v;
for(i=1;i<=n;++i) d[i] = maxData; //此处将除源点以外的所有点的距离设置成无穷大
d[src] = 0;
memset(final,0,sizeof(final)); //注意此处将所有的点(包括源点)设置为false,因为d[]距离没有进行初始化(即赋值为源点的距离)
for(i=1;i<n;++i)
{
mintemp = maxData;
v = -1;
for(j=1;j<=n;++j)
{
if(!final[j] && d[j]<mintemp)
{
v = j;
mintemp = d[j];
}
}
if(v==-1) break; //此处进行了优化
final[v] = true;
for(j=0;j<adj[v].size();++j) //遍历v的邻接表
{
int tempv = adj[v][j].v;
int tempw = adj[v][j].w;
if(d[tempv]>d[v]+tempw)
{
d[tempv] = d[v]+tempw;
}
}
}
}
int main()
{
int i,j;
int start,end,weight;
edge tempedge;
while(scanf("%d%d",&m,&n)!=-1)
{
for(i=1;i<=m;++i)
{
scanf("%d%d%d",&start,&end,&weight);
tempedge.v = end;
tempedge.w = weight;
adj[start].push_back(tempedge);
tempedge.v = start;
adj[end].push_back(tempedge);
}
}
return 0;
}
最短路径小结之Floyd算法
/*最短路径小结之Floyd算法
每次加入一个点进行更新路径,
更新n次
*/
for(k=0;k<n;k++)//每次添加第k个节点进行更新
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if(A[i][j]>(A[i][k]+A[k][j]))
{
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
POJ3253 最短路径小结之Bellman-Ford算法
/*POJ3253 最短路径小结之Bellman-Ford算法
由于Dijkstra算法存在不能处理负权的弊端,Bellman-Ford算法被开发了出来
个算法将Dijkstra算法使用到的三角不等式成为“松弛操作”,
通过反复的松弛操作使得整个图中各边的路径长度不断地逼近其最短路径长,
直至算法结束时每条边的路径长度均达到其最短路径长
这种算法有效地解决的负权问题,同时对于含负权环的问题,
这种算法也可以输出是否存在负权环。
表象:具有dij那样的三角不等式,不具有每次从源点寻找的最短路径的操作
时间效率是O(VE).
POJ3253
题目大意:农夫FJ喜欢在自己的农场里面玩穿越,
而虫洞-----就是就是一种是时间倒退的时光机(单向的)
但是普通道路是双向的(a bidirectional path)
求判断会不会穿越成功看到从前的自己
*/
#include<iostream>
using namespace std;
#define INF 0x7FFFFFF
#define N 6000
struct E
{
int a,b,tm;
}e[N]; //用邻接表
int n,m,w,num;
void fi()
{
int a,b,tm;
num=1;
/*此处的邻接表只是数组结构体而已
保存了所有的边的起点和终点
这和Bellman-Ford算法对于每个节点加入m条边进行松弛有关
这也是算法时间效率为O(VE)的原因
*/
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&tm);
e[num].a=a; e[num].b=b; e[num].tm=tm;
num++;
e[num].b=a; e[num].a=b; e[num].tm=tm;
num++;
}
for(int i=1;i<=w;i++)
{
scanf("%d%d%d",&a,&b,&tm);
e[num].a=a; e[num].b=b; e[num].tm=(-1)*tm;
num++;
}
}
int bellman_frod()
{
int dis[N];
for(int i=1;i<=n;i++)
dis[i]=INF;
dis[1]=0;//以这个为源点
for(int i=1;i<n;i++)//松弛n-1次就必定找出最短路径了
{
for(int j=1;j<num;j++)
if(dis[e[j].b]>dis[e[j].a]+e[j].tm)
dis[e[j].b]= dis[e[j].a]+e[j].tm;
}
for(int j=1;j<num;j++)//再来一次判断有无负环
if(dis[e[j].b]>dis[e[j].a]+e[j].tm)
return 1;//注意哦~~1是表示有负环就是可以时空倒转
return 0;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
fi();
if(bellman_frod()==1)
printf("YES\n");
else
printf("NO\n");
}
system("pause");
return 0;}
/*
总结(1)判断负环的时候,就是前n-1次扫描已经找出最短路径,如果第
n次扫描还有边的值变化的话就说明有负环
(2)、在终点U变化的过程中,dis[]里面装的就是从加入1条、2条、3条边——
到E条边的值的最小值
*/
最短路径小结之SPFA算法
/*最短路径小结之SPFA算法
Bellman-Ford的复杂度相当的大,所以SPFA算法应运而生
前者能处理负环和负权图 并能求出两图的最短路径
后者能处理负权 并能求出最短路径
后者也能处理负环图(某个点弹出队列次数超过n-1次),但是不能求出最短路径
这个算法使用到了队列对需要处理的节点进行维护,
而不是像Bellman-Ford算法一样不管这个节点需不需要维护都对其进行判断
floyd通过点来松弛,
Bellman-Ford和SPFA通过边来松弛
*/
/*或者原理:某一个节点入队n次的话则存在负环*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<queue>
#include<algorithm>
using namespace std;
#define inf 1000000000
#define maxn 10000
int visited[maxn],dis[maxn],st[maxn];
int ff[505];
int n,m,h;
int num;
int yes;
struct node
{
int x;
int value;
int next;
}e[maxn];
void init()
{
int u,v,w;
memset(st,0,sizeof(st));
memset(e,0,sizeof(e)); ///
int num=1;
cin>>n>>m>>h;
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
e[num].x=v;
e[num].value=w;
e[num].next=st[u];
st[u]=num;
num++;
e[num].x=u;
e[num].value=w;
e[num].next=st[v];
st[v]=num;
num++;
}
for(int i=1;i<=h;i++)
{
cin>>u>>v>>w;
e[num].x=v;
e[num].value=-w; //negetive
e[num].next=st[u];
st[u]=num;
num++;
}
}
void init2()
{
memset(ff,0,sizeof(ff)); //判断节点出队的次数的数组
}
void spfa()
{
for(int i=1;i<=n;i++)
{
visited[i]=0;
dis[i]=inf;
}
visited[1]=1;
dis[1]=0;
queue<int>que;
que.push(1);
ff[1]++; //note
int tmp=st[1];
int cur;
int k=0;
/*一开始push起始点进入队列,通过能够松弛的点且未在队列里面的就push进队列
每次取队首的点利用邻接表对其出边进行松弛,直到队列为空
*/
while(!que.empty())//松弛的方式改变,但是松弛三角形还是不变
{
cur=que.front();
tmp=st[cur];
que.pop();
visited[cur]=0;
while(tmp!=0)
{
if(dis[e[tmp].x]>dis[cur]+e[tmp].value)
{
dis[e[tmp].x]=dis[cur]+e[tmp].value; ///
if(visited[e[tmp].x]==0) ///
{
que.push(e[tmp].x); ///
ff[e[tmp].x]++; //note2
if(ff[e[tmp].x]>=n)//判断是否存在负环的关键
{ yes=1; goto end; }
visited[e[tmp].x]=1;
}
}
tmp=e[tmp].next;
}
}
end:
if(yes==1)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
int main()
{
int test;
cin>>test;
while(test--)
{
init();
init2();
yes=0; //判断变量
spfa();
}
//system("pause");
return 0;}
POJ2002 坐标的点的二分查找
/*POJ2002 坐标的点的二分查找
给 N 个点,任意取其中4个,是否能构成正方形,
能的话算一组,求有多少组这样的点,也就是说最多可以找出多少个正方形
方法很简单,确定两个点,必然可以求出正方形的另外两个点,
在点集中查找,另外两个点是否存在就可以了(用二分查找)
用哈希也行,但是这样会较大增大代码量
map在这貌似不行
*/
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
#define N 1005
struct point
{
double x,y;
};
point p[N];
int n;
bool operator ==(point a,point b)//运算符重载
{
if( a.x==b.x && a.y==b.y )return true;
return false;
}
bool operator >(point a,point b)//判断大的情况的运算符重载
{
if(a.x>b.x || a.x==b.x && a.y>b.y)return true;
return false;
}
bool operator <(point a,point b)
{
if(a.x<b.x || a.x==b.x && a.y<b.y)return true;
return false;
}
int cmp(const void *a,const void *b)
{
return *(point *)a > *(point *)b ? 1:-1;
}
bool find(point a,int m)
{
int begin = 0,end = m-1;
while( begin <= end )
{
int mid = ( begin + end )/2;
if( a.x==p[mid].x && a.y==p[mid].y )return true;
if( a.x>p[mid].x || a.x==p[mid].x && a.y>p[mid].y )
begin = mid + 1;
else end = mid - 1;
}
return false;
}
point Whirl(double cosl, double sinl, point a, point b)
// ab 绕 a 逆时针转过角度 A 得到的点,sinl=sinA,cosl=cosA
{
b.x -= a.x; b.y -= a.y;
point c;
c.x = b.x * cosl - b.y * sinl + a.x;
c.y = b.x * sinl + b.y * cosl + a.y;
return c;
}
int judge(int i,int j)
{
point pa,pb,po;
if(i==j)return 0;
po.x=(p[i].x+p[j].x)/2.0;
po.y=(p[i].y+p[j].y)/2.0;
pa=Whirl(0.0,1.0,po,p[i]);//逆时针90度旋转
pb=Whirl(0.0,1.0,po,p[j]);//顺时针旋转
if(!find(pa,n)) return 0;
else if(!find(pb,n)) return 0;
return 1;
}
int main()
{
int i,j;
while(scanf("%d",&n),n)
{
for(i=0;i<n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
qsort(p,n,sizeof(p[0]),cmp);
int cnt=0;
for(i=0;i<n;i++)
{
for(j=i+1;j<n;j++)
if(judge(i,j)==1)cnt++;
}
printf("%d\n",cnt/2);
}
return 0;
}
HDU1166以元素为端点的线段树
/*HDU1166以元素为端点的线段树(单点线段树:权值在点上)
区间的定义不同则树的定义也不一样
静态建立线段树,所以内存占用就是节点数的多少
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
struct iitree{
int lc,rc;
int data;
}tree[200000];//一开始是50050(竟然要开到3、4倍),空间果然浪费居多呀
void print(int u,int v){//调试所用(输出非叶子节点的值)
/**/
for(int i=u;i<=v;i++)
cout<<"i tree[i].data"<<i<<" "<<tree[i].data<<endl;
}
void print(int p,int u,int v){//调试所用(输出叶子节点的值)
if(u==v){
cout<<"u, v p "<<u<<" "<<v<<" "<<p<<" "<<tree[p].data<<endl;
return;
}
int mid=(u+v)/2;
print(p*2,u,mid);
print(p*2+1,mid+1,v);
}
/*
create的作用,利用一维数组建立一棵静态树
同时利用结构体保存好孩子关系
u,v就是引导建立一个u~v范围的一棵树
*/
void create(int p,int u,int v){//单点线段树
tree[p].lc=u;
tree[p].rc=v;
tree[p].data=0;
if(u==v)return ;
int mid=(u+v)/2;
create(p*2,u,mid);//mid,mid+1体现单点
create(p*2+1,mid+1,v);
}
/*
我一开始的想法很怪,
就是以为破开mid,就直接用mid去比较得了
但是这样致使步调不一致,因为mid的下标还要额外处理
但是如果只是比较两边的边界,那么只需破到叶子节点即可,步调更一致*/
/*关于插入,由于给定单元素,而且树提前就建好了,所以就相当要遍历整棵树
来找到i这一点*/
void insert(int p,int i,int x){
int l=tree[p].lc ,r=tree[p].rc;
/*
tree[p].data+=x;之所以要提出外面,为的是在插入过程的整条路径都加入这个数
实现非叶子节点的data也能够存数,而且存的是当前区间的数
而叶子节点就非常特殊了,存的是区间长度为1的数
其他的做法是:利用子节点求出父节点的data(递归回来的时候顺带即可)
*/
tree[p].data+=x;
if(l==i && r==i){//每次破开中间的数,判断是否就是要找的数
//tree[p].data+=x;
return;
}
int mid=(l+r)/2;
if(i<=mid)
insert(p*2,i,x);
else
insert(p*2+1,i,x);
}
/*
有三个边界不是很清楚:p的边界u,v
tree[p].u tree[p].v
i
*/
/*至于区间查询,带有给定的区间,所以有外来的u,v和树本身的“u,v”*/
int res;
void query(int p,int u,int v){
if(tree[p].lc==u && tree[p].rc==v){
//cout<<"*****"<<p<<" "<<tree[p].data<<endl;
res+=tree[p].data;
return;
}
int l=tree[p].lc ,r=tree[p].rc;
int mid=(l+r)/2;
if(u>=mid+1){
query(p*2+1,u,v);
}
else if(v<=mid){
query(p*2,u,v);
}
else{
query(p*2,u,mid);
query(p*2+1,mid+1,v);
}
}
int main(){
int t,n;
scanf("%d",&t);
int ncase=1,flag;
while(t--){
flag=0;
scanf("%d",&n);
create(1,1,n);
int tmp;
for(int i=1;i<=n;i++){
scanf("%d",&tmp);
insert(1,i,tmp);
}
//print(1,15);
//print(1,1,10);
char str[10];
int a,b;
while(scanf("%s",&str)){
if(strcmp(str,"End")==0){
break;
}
scanf("%d%d",&a,&b);
if(str[0]=='Q'){
res=0;
query(1,a,b);
if(flag==0){
printf("Case %d:\n",ncase++);
flag=1;
}
printf("%d\n",res);
}
else if(str[0]=='A'){
insert(1,a,b);
}
else{
insert(1,a,-b);
}
}
}
return 0;}
今天小结到这。。
明天继续。。
2013-10-9
计算几何
HDU1086 判断线段是否相交(没有求出交点)
/*HDU1086 判断线段是否相交(没有求出交点)
(1)排斥实验
两个线段组成的矩形要相交
(2)跨立实验
叉乘是否异号。对两条线段都要进行叉乘
(P3-P1)×(P2-P1)与(P4-P1)×(P2-P1)的符号是否相同,
相同则不跨立,线段也就不相交,否则相交
*/
#include<iostream>
#include<stdio.h>
using namespace std;
int i,k,n;
int count;
struct point
{
double x,y;
};
struct segment
{
point a,b;
}s[101];
bool inte(point & a,point & b,point & c,point & d)
{ //改成引用的一个好处就是避免用指针时候的->
//.而用点号则更好看些。引用就可以用,因为当是对象了。
if(!(min(a.x,b.x)<=max(c.x,d.x)&&
min(c.x,d.x)<=max(a.x,b.x)&&
min(a.y,b.y)<=max(c.y,d.y)&&
min(c.y,d.y)<=max(a.y,b.y)
))return false;
double u,v,w,z;
u=(b.x-a.x)*(c.y-a.y)- (c.x-a.x)*(b.y-a.y) ;
v=(b.x-a.x)*(d.y-a.y)- (d.x-a.x)*(b.y-a.y) ;
w=(d.x-c.x)*(a.y-c.y)- (a.x-c.x)*(d.y-c.y) ;
z=(d.x-c.x)*(b.y-c.y)- (b.x-c.x)*(d.y-c.y) ;
return (u*v<=0.00000001&&w*z<=0.00000001);//包含了特殊情况2个
/*
特殊情况:(1)首位相接,(2)一者插足中间
*/
//其实这里隐含了几点:如果都等于0的话就是说共线,但是此题应该没有这种情况
//即使有一边等于0的话也是早在排斥实验的时候被排除,因为线段没有长度,就谈不上相交了
//但是有一边相等的话还有一边的一端在另外一边的情况,所以还是要有等于号的判断
}
int main(){
int count;
while(cin>>n&&n)
{
count=0;
for(i=0;i<n;i++)
{ cin>>s[i].a.x>>s[i].a.y>>s[i].b.x>>s[i].b.y; }
for(i=0;i<n;i++)
for(k=i+1;k<n;k++)
if(inte(s[i].a,s[i].b,s[k].a,s[k].b))
count++ ;
cout<<count<<endl;
}
//system("pause");
return 0;}
POJ1269判断直线相交&求直线交点
/*POJ1269判断直线相交&求直线交点
这可以运用克莱姆法则列方程求出来
题目大意:求直线相交交点、或者判断共线,比线段相交简单些,
(前者可利用方程,后者要先判断是否相交,然后用面积法求得交点)
思路:克莱姆法则
题目大意:给出四个点确定两条直线。如果是一条线输出“LINE”,如果平行输出“NONE”
如果有交点输出交点坐标。
思路:注意是直线,不是线段啊。
用两点式推出两条直线方程 :
(y1 - y2)x + (x2 - x1)y = x2y1 - x1y2;
(y3 - y4)x + (x4 - x3)y = x4y3 - x3y4;
又由Cramer法则: a1x + b1y = c1;
a2x + b2y = c2;
(D是系数的行列式)(Dj是在D中的行列式,用方程右边的常数去替换D中j列的数)
D = a1b2 - a2b1;
D1 = c1b2 - c2b1;
D2 = a1c2 - a2c1;
D != 0时, x = D1 / D, y = D2 / D; 得到交点坐标。
D = 0时;如果a1 / a2 = b1 / b2 = c1 / c2 , 则四个点在一条直线上;
否则两直线平行,无交点。
*/
#include <stdio.h>
#include <stdlib.h>
int main () {
int n;
int x1, y1, x2, y2, x3, y3, x4, y4;
int a1, b1, c1, a2, b2, c2;
int D, D1, D2;
double x, y;
scanf ("%d", &n);
printf ("INTERSECTING LINES OUTPUT\n");
while (n--) {
scanf ("%d%d%d%d%d%d%d%d",&x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4);
a1 = y1 - y2; // 由公式推出的关系
a2 = y3 - y4;
b1 = x2 - x1;
b2 = x4 - x3;
c1 = x2 * y1 - x1 * y2;
c2 = x4 * y3 - x3 * y4;
D = a1 * b2 - a2 * b1;
if (D) { // D != 0 运用Cramer法则求交点
D1 = c1 * b2 - c2 * b1;
D2 = a1 * c2 - a2 * c1;
x = (double)((double)D1 / D);
y = (double)((double)D2 / D);
printf ("POINT %.2lf %.2lf\n", x, y);
}
else
if (a1 * c2 == a2 * c1 && b1 * c2 == b2 * c1) // 两直线重合
printf ("LINE\n");
else
printf ("NONE\n");
}
printf ("END OF OUTPUT\n");
//system ("pause");
return 0;
}
判断线段相交及求规范性相交交点(黑书权威解法)
/*判断线段相交及求规范性相交交点(黑书权威解法)
规范相交:两条线段恰有唯一一个不是端点的公共点
非规范相交:
叉积结果的绝对值等于平行四边形的面积
两线段AB、CD “规范相交”:
ABXAC*ABXAD<0 && CDXCA*CDXCB<0(貌似等于0解决了很多非规范的情况-_-)
*/
/*关键代码:*/
#include<iostream>
#include<math.h>
#include<iomanip>
using namespace std;
#define precision 0.000001
struct point{
double x,y;
};
/*判断浮点数x和0的大小关系
(1)如果x绝对值很小,这可以等于0(在0的某个小邻域里面成立)
否则(2)(3):
(2)如果x>0则是大于0的数
(3)如果x<0则是小于0的数
这也引出了比较两个浮点数是否相等的做法,
fabs(a-b)与precision比较即可
*/
int dblcmp(double d){
if(fabs(d)<precision)
return 0;
return (d>0)?1:-1;
}
/*叉积*/
double det(double x1,double y1,double x2,double y2){
return x1*y2-x2*y1;
}
/*叉乘*/
double cross(point a,point b,point c){
return det(b.x-a.x,b.y-a.y,c.x-a.x,c.y-a.y);
}
/*判断线段是否相交(仅包含规范相交)
a^b==-1等价于aXb<0或者aXb==-1*/
int segcrossSimple(point a,point b,point c,point d){
return (dblcmp(cross(a,c,d))^dblcmp(cross(b,c,d)))==-2&&
(dblcmp(cross(c,a,b))^dblcmp(cross(d,a,b)))==-2;
}
double dotdet(double x1,double y1,double x2,double y2){
return x1*x2+y1*y2;
}
double dot(point a,point b,point c){
return dotdet(b.x-a.x,b.y-a.y,c.x-a.x,c.y-a.y);
}
int betweenCmp(point a,point b,point c){
return dblcmp(dot(a,b,c));
}
/*判断线段是否相交(规范相交和非规范相交都包含)
返回:0 不相交 ,1 规范相交 ,2 非规范相交*/
int segcross(point a,point b,point c,point d,point &p){
double s1,s2,s3,s4;
int d1,d2,d3,d4;
d1=dblcmp(s1=cross(a,b,c));
d2=dblcmp(s2=cross(a,b,d));
d3=dblcmp(s3=cross(c,d,a));
d4=dblcmp(s4=cross(c,d,b));
//规范相交求交点
if((d1^d2)==-2 && (d3^d4)==-2){
p.x=(c.x*s2-d.x*s1)/(s2-s1);
p.y=(c.y*s2-d.y*s1)/(s2-s1);
return 1;
}
//非规范相交判断,但是求不了交点
if(d1==0 && betweenCmp(c,a,b)<=0 ||
d2==0 && betweenCmp(d,a,b)<=0 ||
d3==0 && betweenCmp(a,c,d)<=0 ||
d4==0 && betweenCmp(b,c,d)<=0)
return 2;
return 0;
}
int main(){
point a,b,c,d;
while(1){
cin>>a.x>>a.y>>b.x>>b.y;
cin>>c.x>>c.y>>d.x>>d.y;
point p;
if(segcrossSimple(a,b,c,d)==1)
cout<<"相交"<<endl;
else
cout<<"不相交"<<endl;
if(segcross(a,b,c,d,p)==1){
cout<<"规范性相交 交点: ";
cout<<fixed<<setprecision(3)<<p.x<<" "<<p.y<<endl;
printf("%.2lf %.2lf\n",p.x,p.y);
}
else if(segcross(a,b,c,d,p)==2){
cout<<"非规范性相交"<<endl;
}
else{
cout<<"不相交"<<endl;
}
}
system("pause");
return 0;}
判断一个点在多边里面还是外面
/*判断一个点在多边里面还是外面
法一:
*/
#include <stdio.h>
#include <math.h>
#include <iostream>
using namespace std;
#define inf 1e9
#define exp 1e-9
struct node
{
double x;
double y;
};
node nodes[105], p;
struct segment
{
node st;
node en;
};
int n;
//叉积 if > 0 p1在p2的顺时针方向
double multi(node p1, node p2, node p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);
}
//if叉积为零说明共线,如果该点在以side为对角线的矩形中说明p在side上
int isonline(node p, segment side)
{
return (fabs(multi(p, side.st, side.en)) < exp &&
min(side.st.x, side.en.x) <= p.x && p.x <= max(side.st.x, side.en.x) &&
p.y >= min(side.st.y, side.en.y) && p.y <= max(side.st.y,side.en.y));
}
//线段相交,跨立试验(叉积判断),快速排斥试验
int intersect(segment l, segment r)
{
return ( max(l.st.x, l.en.x) >= min(r.st.x, r.en.x)//快速排斥试验用来确定以两线段为对角线的矩形是否相交
&& max(r.st.x, r.en.x) >= min(l.st.x, l.en.x)
&& max(l.st.y, l.en.y) >= min(r.st.y, r.en.y)
&& max(r.st.y, r.en.y) >= min(l.st.y, l.en.y)
&& multi(r.st, l.en, l.st) * multi(r.en, l.en, l.st) <= 0
&& multi(l.st, r.en, r.st) * multi(l.en, r.en, r.st) <= 0);
}
int isinpolygon(node p)
{
int i, count = 0;
segment line, side;
if(n == 1) return fabs(p.x - nodes[0].x) < exp && fabs(p.y - nodes[0].y) < exp;
if(n == 2)
{
side.st = nodes[0];
side.en = nodes[1];
return isonline(p, side);
}
line.st = p;
line.en.y = p.y;
line.en.x = -inf;
for( i = 0; i < n; i ++ )
{
side.st = nodes[i];
side.en = nodes[(i + 1) % n];
if(isonline(p,side)) return 1;// 如果p在side上
if(fabs(side.st.y - side.en.y) < exp) continue;//如果是水平的则不考虑
//两端点相交line,取y坐标大的以避免重复计数
if(isonline(side.st, line) && side.st.y > side.en.y) count ++;
else if(isonline(side.en, line) && side.en.y > side.st.y) count ++;
else if(intersect(line, side)) count ++;
}
return count % 2;
}
int main()
{
while(scanf("%d",&n) && n)
{
for(int i = 0; i < n; i ++)
scanf("%lf%lf",&nodes[i].x, &nodes[i].y);
scanf("%lf%lf",&p.x,&p.y);
if(isinpolygon(p)) printf("Within\n");
else printf("Outside\n");
}
return 0;
}
/*
法二:
#include <stdio.h>
#include <iostream>
using namespace std;
#define M 105
struct node
{
double x, y;
}p[M];
int inpolygon(node t, int n)
{
int i, t1, t2, sum, f;
for(i = 0; i <= n; i ++) p[i].x -= t.x, p[i].y -= t.y;
t1 = p[0].x>=0 ?(p[0].y>=0?0:3) :(p[0].y>=0?1:2);
for(sum = 0, i = 1; i <= n; i ++)
{
if(!p[i].x && !p[i].y) break;
f = (int)(p[i].y * p[i-1].x - p[i].x * p[i-1].y);
if(!f && p[i-1].x*p[i].x <= 0 && p[i-1].y*p[i].y <= 0) break;
t2 = p[i].x>=0 ?(p[i].y>=0?0:3) :(p[i].y>=0?1:2);
if(t2 ==(t1 + 1) % 4) sum += 1;
else if(t2 ==(t1 + 3) % 4) sum -= 1;
else if(t2 ==(t1 + 2) % 4)
{
if(f > 0) sum += 2;
else sum -= 2;
}
t1 = t2;
}
if(i<=n || sum) return 1;
return 0;
}
int main()
{
int n, i;
node t;
while(scanf("%d", &n) && n)
{
for(i = 0; i < n; i ++)
scanf("%lf%lf", &p[i].x, &p[i].y);
p[n] = p[0];
scanf("%lf%lf", &t.x, &t.y);
if(inpolygon(t, n)) printf("inside\n");
else printf("outside\n");
}
return 0;
}
*/
凸包:仅输出凸包的顶点
/*
凸包:仅输出凸包的顶点
*/
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
//此凸包作用:逆时针输出凸包顶点,凸包边上的点不输出
//(要全部输出边上的点的话,暂时没想到咋办,囧)
struct point
{
int x,y;
}p[105],res[105];
double Dist(const point &arg1, const point &arg2)
{
return sqrt((double) ((arg1.x - arg2.x)*(arg1.x - arg2.x) + (arg1.y - arg2.y)*(arg1.y - arg2.y)) );
}
int multi(int i,int top,point p2,point p1,point p0)
{
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int mysort1(point a,point b)
{
if(a.y!=b.y) return a.y<b.y;//重大修改:下左点改为上左点
//if(a.y!=b.y) return a.y>b.y;
if(a.y==b.y&&a.x!=b.x) return a.x<b.x;
}
bool cmp(const point &a,const point &b)
{
point temp=p[0];
double xmt=(a.x-temp.x)*(b.y-temp.y)-(b.x-temp.x)*(a.y-temp.y);
if(xmt)
return xmt>0;
return Dist(a,temp)<Dist(b,temp);
}
int main()
{
int n,k,len;
cin>>k;
while(k--)
{
cin>>n;
for(int i=0;i<n;i++)
cin>>p[i].x>>p[i].y;
sort(p,p+n,mysort1);//排序,找到最左下角的点
res[0]=p[0];
sort(p+1,p+n,cmp);//按照极角排序
res[1]=p[1];
int top=1;
for(int i=2;i<n;i++)//i=2:好把共线的点弹出去
{
while( top && multi(i,top,p[i],res[top],res[top-1])<=0)
top--;
res[++top]=p[i];
}
for(int i=0;i<=top;i++)
cout<<res[i].x<<" "<<res[i].y<<endl;
}
system("pause");
return 0;}
凸包:输出凸包边上的所有边
/*
凸包:输出凸包边上的所有边
不完善,囧
*/
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
/*
PointSet[]: 输入的点集
ch[]:输出的凸包的点集,按照逆序输出
n:PointSet中点的数目
len:输出的凸包上的点的个数
*/
struct Point{
float x,y;
};
//小于说明向量p0p1的极角大于p0p2的极角
float multiply(Point p1,Point p2,Point p0)
{
return ((p1.x-p0.x)*(p2.y-p0.y) -(p2.x-p0.x)*(p1.y-p0.y));
}
float dis(Point p1,Point p2)
{
return sqrt((p1.x-p2.x)*(p1.x -p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
}
void Graham_scan(Point PointSet[],Point ch[],int n,int &len)
{
int i,j,k=0,top = 2;
Point tmp;
for (i=1;i<n;i++)
if ((PointSet[i].y<PointSet[k].y ||((PointSet[i].y==PointSet[k].y)) &&(PointSet[i].x <PointSet[k].x)))
k=i;
tmp =PointSet[0];
PointSet[0]=PointSet[k];
PointSet[k]=tmp;
//按极角大小排序
for (i=1;i<n-1;i++)
{
k=i;
for (j=i+1;j<n;j++)
{
if ((multiply(PointSet[j],PointSet[k],PointSet[0])>0)
||((multiply(PointSet[j],PointSet[k],PointSet[0])==0)
&& (dis(PointSet[0],PointSet[j]) < dis(PointSet[0],PointSet[k]))))
{
tmp = PointSet[j];
PointSet[j] = PointSet[k];
PointSet[k] = tmp;
}
}
}
ch[0] = PointSet[0];
ch[1] = PointSet[1];
ch[2] = PointSet[2];
for (i=3;i<n;i++)
{
while (multiply(PointSet[i],ch[top],ch[top-1])>0)
top--;
ch[++top] = PointSet[i];
}
len = top+1;
}
const int maxN = 1000;
Point PointSet[maxN];
Point ch[maxN];
int n;
int len;
int cmp(const void *a,const void *b)
{
Point pa = *(Point *)a;
Point pb = *(Point *)b;
if (pa.x>pb.x)
return 1;
else if((pa.x ==pb.x) &&pa.y>pb.y)
return 1;
else return -1;
}
void solve()
{
int n,i;
cin>>n;
for (i=0;i<n;i++)
cin>>PointSet[i].x>>PointSet[i].y;
Graham_scan(PointSet,ch,n,len);
qsort(ch,len,sizeof(ch[0]),cmp);
//cout<<"****"<<endl;
for (i=0;i<len;i++)
cout<<ch[i].x<<" "<<ch[i].y<<endl;
}
int main()
{
int n;
cin>>n;
while (n--)
{
solve();
}
}
POJ1457 建立源点汇点的最大流
/*POJ1457 建立源点汇点的最大流
编号多加了两个,建立起源点到发电站的边
建立边的时候记得建立反向边
求最大流关键要知道每条边的容量
建立源点到发电站,边的容量为发电站的最大输出
建立消费者到汇点,边的容量为消费者的最大消费
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<queue>
#include<algorithm>
using namespace std;
#define inf 1000000000
#define maxn 150
#define maxm 20500 //note!! 如果RE就是这里出的原因
int num;//note1
int n,np,nc,m;
struct node
{
int c,next,to;
}e[maxm]; //note2
int box[maxn];//链式前向星
void init1()
{
num=0;
memset(box,-1,sizeof(box));//按道理应该是初始化为-1的
//int aa,bb,w,u,v;
int u,v,w;
char yy[20];
for(int i=1;i<=m;i++)
{
scanf("%s",yy);
sscanf(yy,"(%d,%d)%d",&u,&v,&w);
u+=2;//统一加2
v+=2;
e[num].to=v;
e[num].c=w;
e[num].next=box[u];
box[u]=num;
num++;
///-------
e[num].to=u;
e[num].c=0;//note!! 反向边
e[num].next=box[v];
box[v]=num;
num++;
///-------
}
for(int i=1;i<=np;i++)
{
scanf("%s",yy);
sscanf(yy,"(%d)%d",&v,&w);
u=1; //原点设为1,所以原来的点要往后推2
v+=2;
e[num].to=v;
e[num].c=w;
e[num].next=box[u];
box[u]=num;
num++;
///-------
e[num].to=u;
e[num].c=0;//note!!
e[num].next=box[v];
box[v]=num;
num++;
///-------
}
for(int i=1;i<=nc;i++)
{
scanf("%s",yy);
sscanf(yy,"(%d)%d",&u,&w);
u+=2;
v=n+2;
e[num].to=v;
e[num].c=w;
e[num].next=box[u];
box[u]=num;
num++;
///-------
e[num].to=u;
e[num].c=0;//note!!
e[num].next=box[v];
box[v]=num;
num++;
///-------
}
}
int sap(int start,int end,int nnn)
{
int numh[maxn],h[maxn],cur[maxn],pre[maxn];
int cur_flow,flow_ans=0,u,tmp,neck,i;
memset(h,0,sizeof(h));
memset(numh,0,sizeof(numh));///
memset(pre,-1,sizeof(pre));///
for(int i=1;i<=nnn;i++)
cur[i]=box[i];
numh[0]=nnn;
u=start;
while(h[start]<nnn)
{
if(u==end)
{
cur_flow=inf;
for(i=start;i!=end;i=e[cur[i]].to)
{
if(cur_flow>e[cur[i]].c)
{
neck=i;
cur_flow=e[cur[i]].c;
}
}
for(i=start;i!=end;i=e[cur[i]].to)
{
tmp=cur[i];
e[tmp].c-=cur_flow;
e[tmp^1].c+=cur_flow;
}
flow_ans+=cur_flow;
u=neck;
}
for(i=cur[u];i!=-1;i=e[i].next)
if(e[i].c&&h[u]==h[e[i].to]+1)
break;
if(i!=-1)
{
cur[u]=i;
pre[e[i].to]=u;
u=e[i].to;
}
else
{
if(0==--numh[h[u]])break;
cur[u]=box[u];
for(tmp=nnn,i=box[u];i!=-1;i=e[i].next)
if(e[i].c)
tmp=min(tmp,h[e[i].to]);
h[u]=tmp+1;
++numh[h[u]];
if(u!=start)u=pre[u];
}
}
return flow_ans;
}
int main()
{
while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF)
{
init1();
int love;
love=sap(1,n+2,n+2);//n+2个点
printf("%d\n",love);
}
//system("pause");
return 0;}
2013-10-11
二分图
HDU2063过山车 二分图最大匹配:
/*
HDU2063过山车 二分图最大匹配:在二分图中,任意不同顶点的边数最大的匹配集合
完全匹配:每个顶点都有匹配
题目大意:男女搭配,二分图模板题
思路:匈牙利算法.O(n*m)时间效率.枚举女生集合里面的每一个女生,每次都去找增广路径,
增广路径:
(1)两个点没有匹配过的,
(2)出发点和终点没有匹配过的,和已经匹配过的交替呈现 这里DFS搜就是了.
*/
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
typedef long long ll;
#define clr(a) memset((a),0,sizeof (a))
#define rep(i,a,b) for(int i=(a);i<(int)(b);i++)
#define per(i,a,b) for(int i=((a)-1);i>=(int)(b);i--)
#define inf 0x7ffffff
#define eps 1e-6
using namespace std;
int k,m,n;
int b[510];//boy
int g[510];//girl
int flag[510];
int mm[510][510];//map
void init(){
int vm,vn;
memset(b,-1,sizeof(b));
memset(g,-1,sizeof(g));//赋值为-1意思更贴题
memset(mm,0,sizeof(mm));
for(int i=1;i<=k;i++){
scanf("%d%d",&vm,&vn);
mm[vm][vn]=1;
}
//system("pause");
}
int findpath(int gu){
for(int i=1;i<=n;i++){
if(mm[gu][i]==1 && !flag[i]){
flag[i]=1;
if(b[i]==-1){//两个点都是未在以前的增广路径(匹配结果)中的,直接找到了增广路径
b[i]=gu; //更新匹配
g[gu]=i;
return 1;
}
else if(findpath(b[i])){//如果这个男的已经匹配过了,那么沿着他的女伴找增广路径
b[i]=gu;
g[gu]=i;
return 1;
}
}
}
return 0;
}
int XYL(){
int res=0;
for(int i=1;i<=m;i++){//m是女生的个数
if(g[i]==-1){//如果还没有在匹配的就找增广路径
memset(flag,0,sizeof(flag));
//每次进行找增广路径的时候不能走已经用过的点,有些点会回头的
//但是b[]g[]数组不改变是因为不断的寻找增广路径
//点可以已经在增广路径中,但是不能在当前要寻找的增广路径中出现2遍
res+=findpath(i);
}
}
return res;
}
int main(){
int test;
//cin>>test;
while(~scanf("%d",&k)){
if(k==0)break;
scanf("%d%d",&m,&n);
init();
printf("%d\n",XYL());
};
//system("pause");
return 0;
}
HDU1281 巧妙的二分匹配(兼效率最高)
/*HDU1281 巧妙的二分匹配(兼效率最高)
注:此程序不完整
利用二分图的性质:
一边的i是行,另一边的j是列
则通过二分图匹配出来的点(Aij)绝不会在同一行且不在同一列!妙极
所以根据给出的ij做出二分图,明显nx=ny,
求出最大匹配即可。
而重要点的定义:当且A是重要点时,最大匹配的必要条件:A必存在。
所以枚举每个点判断每个点是否是重要点即可
因一开始使用vector模拟邻接表,使得后面枚举寸步难行(要删除)囧
固在这只求出最大匹配,不过使用一般的匈牙利算法加上邻接矩阵即可
因为点最多100,边最多100^2,所以时间效率10^6还是在允许之内的
题目大意:问给出车的棋盘位置,问最多有几个车能够在棋盘上存在
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
const int MAXN=1505;//这个值要超过两边个数的较大者,因为有linker
int linker[MAXN];
bool used[MAXN];
vector<int>map[MAXN];
int uN;
bool dfs(int u)
{
for(int i=0;i<map[u].size();i++)
{
if(!used[map[u][i]])
{
used[map[u][i]]=true;
if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))
{
linker[map[u][i]]=u;
return true;
}
}
}
return false;
}
int hungary()
{
int u;
int res=0;
memset(linker,-1,sizeof(linker));
//姑且称为nx,二分图一边的顶点数
/*(1)邻接矩阵存图时
对应dfs里面的for循环是ny,是二分图另一边的顶点数
在for循环里面借助ny枚举nx的所有边
(2)邻接表存图时
*/
for(u=1;u<=uN;u++)
{
memset(used,false,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
//*****************************************************
int main()
{
int n,m,k;
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
for(int i=0;i<MAXN;i++)
map[i].clear();
int ta,tb;
for(int i=0;i<k;i++){
scanf("%d%d",&ta,&tb);
map[ta].push_back(tb);
}
uN=n;
printf("%d\n",hungary());
}
return 0;
}
HDU1498 巧妙的二分图匹配(续)
/*HDU1498 巧妙的二分图匹配(续)
对于每一种颜色求其最大匹配,如果匹配数大于k,
则该种颜色的气球不能爆破
对于每一种颜色求最大匹配原因:
同一种颜色的最大匹配数值上等于 不同行不同列的同种颜色气球最多有几个。
这样就是等于需要爆破的个数
所以对于每种颜色枚举其二分图最大匹配即可
*/
#include<stdio.h>
#include<string.h>
#define M 105
#define N 55
int map[M][M],link[M],flag[M],ans[N],n,k; //注意:link和flag数组长度应该和矩阵的行列数相等;
int find(int i,int color)
{
int j;
for(j=1;j<=n;j++){
if(map[i][j]==color&&flag[j]==0){
flag[j]=1;
if(link[j]==0||find(link[j],color)==1){
link[j]=i;
return 1;
}
}
}
return 0;
}
int getnum(int color)
{
int i,sum;
memset(link,0,sizeof(link));
for(i=1,sum=0;i<=n;i++){
memset(flag,0,sizeof(flag));
if(find(i,color)==1)
sum++;
}
return sum;
}
int main()
{
int i,j,t;
while(scanf("%d%d",&n,&k),n||k){
memset(map,0,sizeof(map));
memset(ans,0,sizeof(ans));
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
scanf("%d",&map[i][j]);
}
}
for(i=1,t=0;i<=50;i++){ //注意这里是遍历每一种颜色,所以循环次数等于所有颜色数
if(getnum(i)>k)
ans[t++]=i;
}
if(t==0)
printf("-1\n");
else{
for(i=0;i<t;i++){
printf(i==0?"%d":" %d",ans[i]);
}
printf("\n");
}
}
return 0;
}
HDU1054 树的最小覆盖数
/*HDU1054 树的最小覆盖数
树的最小覆盖数=二分图(双向图)的最大匹配/2
利用vector模拟邻接表 匈牙利算法(1500的点都没有问题)
算法时间效率(VE)
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
const int MAXN=1505;//这个值要超过两边个数的较大者,因为有linker
int linker[MAXN];
bool used[MAXN];
vector<int>map[MAXN];
int uN;
bool dfs(int u)
{
for(int i=0;i<map[u].size();i++)
{
if(!used[map[u][i]])
{
used[map[u][i]]=true;
if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))
{
linker[map[u][i]]=u;
return true;
}
}
}
return false;
}
int hungary()
{
int u;
int res=0;
memset(linker,-1,sizeof(linker));
//姑且称为nx,二分图一边的顶点数
/*(1)邻接矩阵存图时
对应dfs里面的for循环是ny,是二分图另一边的顶点数
在for循环里面借助ny枚举nx的所有边
(2)邻接表存图时
注意下标从0开始
*/
for(u=0;u<uN;u++)
{
memset(used,false,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
//*****************************************************
int main()
{
int u,k,v;
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<MAXN;i++)
map[i].clear();
for(int i=0;i<n;i++)
{
scanf("%d:(%d)",&u,&k);
while(k--)
{
scanf("%d",&v);
map[u].push_back(v);
map[v].push_back(u);
}
}
uN=n;
printf("%d\n",hungary()/2);
}
return 0;
}
HDU1054 树的最小点覆盖(理论上效率较高算法)
/*
HDU1054 树的最小点覆盖(理论上效率较高算法)
在这题的效率如此之低,都不知道是哪码错了
时间效率理论上比匈牙利算法快O(n^0.5E)
*/
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <queue>
using namespace std;
const int MAXN=1510;//3000都没有问题
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
queue<int>Q;
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1)
{
Q.push(i);
dx[i]=0;
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if(dx[u]>dis) break;
for(int v=0;v<Ny;v++)
if(g[u][v]&&dy[v]==-1)
{
dy[v]=dx[u]+1;
if(My[v]==-1) dis=dy[v];
else
{
dx[My[v]]=dy[v]+1;
Q.push(My[v]);
}
}
}
return dis!=INF;
}
bool DFS(int u)
{
for(int v=0;v<Ny;v++)
if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
{
vst[v]=1;
if(My[v]!=-1&&dy[v]==dis) continue;
if(My[v]==-1||DFS(My[v]))
{
My[v]=u;
Mx[u]=v;
return 1;
}
}
return 0;
}
int MaxMatch()
{
int res=0;
memset(Mx,-1,sizeof(Mx));
memset(My,-1,sizeof(My));
while(searchP())
{
memset(vst,0,sizeof(vst));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1&&DFS(i)) res++;
}
return res;
}
int main()
{
int u,k,v;
int n;
while(scanf("%d",&n)!=EOF)
{
memset(g,0,sizeof(g));
for(int i=0;i<n;i++)
{
scanf("%d:(%d)",&u,&k);
while(k--)
{
scanf("%d",&v);
g[u][v]=1;
g[v][u]=1;
}
}
Nx=n,Ny=n;
printf("%d\n",MaxMatch()/2);
}
return 0;
}
HDU1151 DAG的最小路径覆盖
/*HDU1151 DAG的最小路径覆盖
用尽量少的不相交(可以不规范相交)的简单路径覆盖 有向无环图
二分图(V,v,E)
DAG图的最小路径覆盖数=节点数(|V|)- 最大匹配数(m)
题目大意:求最小路径覆盖数
拆点,建立单向边,构图完毕
套模板的时候注意下标
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
const int MAXN=1505;//这个值要超过两边个数的较大者,因为有linker
int linker[MAXN];
bool used[MAXN];
vector<int>map[MAXN];
int uN;
bool dfs(int u)
{
for(int i=0;i<map[u].size();i++)
{
if(!used[map[u][i]])
{
used[map[u][i]]=true;
if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))
{
linker[map[u][i]]=u;
return true;
}
}
}
return false;
}
int hungary()
{
int u;
int res=0;
memset(linker,-1,sizeof(linker));
//姑且称为nx,二分图一边的顶点数
/*(1)邻接矩阵存图时
对应dfs里面的for循环是ny,是二分图另一边的顶点数
在for循环里面借助ny枚举nx的所有边
(2)邻接表存图时
注意下标从0开始
*/
for(u=1;u<=uN;u++)
{
memset(used,false,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
//*****************************************************
int main()
{
int t;
cin>>t;
while(t--)
{
for(int i=0;i<MAXN;i++)
map[i].clear();
int n,m,ta,tb;
cin>>n>>m;
for(int i=0;i<m;i++)
{
scanf("%d%d",&ta,&tb);
map[ta].push_back(tb);
}
uN=n;
printf("%d\n",n-hungary());
}
return 0;
}
HDU1068 最大独立集
/*HDU1068 最大独立集
选出最大的点集,使得这些点集两两之间没有任何关系
这相当于在原图的E的补集中求最大完全子图的顶点数
二分图的最大独立集数=节点数(n)— 最大匹配数(m)
=最大完全子图的顶点数
注意建图技巧:
本题中给出的是同学之间的亲密关系,并没有指出哪些是男哪些是女,
所以求出的最大匹配数要除以2才是真正的匹配数。
注意:如果不是树的求最大匹配,不必建立双向图
*/
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <queue>
using namespace std;
const int MAXN=1510;//3000都没有问题
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
queue<int>Q;
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1)
{
Q.push(i);
dx[i]=0;
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if(dx[u]>dis) break;
for(int v=0;v<Ny;v++)
if(g[u][v]&&dy[v]==-1)
{
dy[v]=dx[u]+1;
if(My[v]==-1) dis=dy[v];
else
{
dx[My[v]]=dy[v]+1;
Q.push(My[v]);
}
}
}
return dis!=INF;
}
bool DFS(int u)
{
for(int v=0;v<Ny;v++)
if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
{
vst[v]=1;
if(My[v]!=-1&&dy[v]==dis) continue;
if(My[v]==-1||DFS(My[v]))
{
My[v]=u;
Mx[u]=v;
return 1;
}
}
return 0;
}
int MaxMatch()
{
int res=0;
memset(Mx,-1,sizeof(Mx));
memset(My,-1,sizeof(My));
while(searchP())
{
memset(vst,0,sizeof(vst));
for(int i=0;i<Nx;i++)
if(Mx[i]==-1&&DFS(i)) res++;
}
return res;
}
int main()
{
int u,k,v;
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<MAXN;i++)
for(int j=0;j<MAXN;j++)
g[i][j]=0;
for(int i=0;i<n;i++)
{
scanf("%d: (%d)",&u,&k);
while(k--)
{
scanf("%d",&v);
g[u][v]=1;
//g[v][u]=0;
}
}
Nx=n,Ny=n;
printf("%d\n",n-MaxMatch()/2);
}
return 0;
}
815

被折叠的 条评论
为什么被折叠?



